mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 09:58:04 +08:00
merge main
This commit is contained in:
@ -12,7 +12,7 @@ import Button from '@/app/components/base/button'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { createEmptyDataset } from '@/service/datasets'
|
||||
|
||||
type IProps = {
|
||||
interface IProps {
|
||||
show: boolean
|
||||
onHide: () => void
|
||||
}
|
||||
@ -58,7 +58,7 @@ const EmptyDatasetCreationModal = ({
|
||||
<div className={s.tip}>{t('datasetCreation.stepOne.modal.tip')}</div>
|
||||
<div className={s.form}>
|
||||
<div className={s.label}>{t('datasetCreation.stepOne.modal.input')}</div>
|
||||
<Input className='!h-8' value={inputValue} placeholder={t('datasetCreation.stepOne.modal.placeholder') || ''} onChange={e => setInputValue(e.target.value)} />
|
||||
<Input value={inputValue} placeholder={t('datasetCreation.stepOne.modal.placeholder') || ''} onChange={e => setInputValue(e.target.value)} />
|
||||
</div>
|
||||
<div className='flex flex-row-reverse'>
|
||||
<Button className='w-24 ml-2' variant='primary' onClick={submit}>{t('datasetCreation.stepOne.modal.confirmButton')}</Button>
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
fetchDefaultProcessRule,
|
||||
} from '@/service/datasets'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import FloatRightContainer from '@/app/components/base/float-right-container'
|
||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||
@ -52,7 +53,7 @@ import { ModelTypeEnum } from '@/app/components/header/account-setting/model-pro
|
||||
import { Globe01 } from '@/app/components/base/icons/src/vender/line/mapsAndTravel'
|
||||
|
||||
type ValueOf<T> = T[keyof T]
|
||||
type StepTwoProps = {
|
||||
interface StepTwoProps {
|
||||
isSetting?: boolean
|
||||
documentDetail?: FullDocumentDetail
|
||||
isAPIKeySet: boolean
|
||||
@ -202,7 +203,6 @@ const StepTwo = ({
|
||||
}
|
||||
|
||||
const fetchFileIndexingEstimate = async (docForm = DocForm.TEXT, language?: string) => {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams(docForm, language)!)
|
||||
if (segmentationType === SegmentType.CUSTOM)
|
||||
setCustomFileIndexingEstimate(res)
|
||||
@ -211,6 +211,10 @@ const StepTwo = ({
|
||||
}
|
||||
|
||||
const confirmChangeCustomConfig = () => {
|
||||
if (segmentationType === SegmentType.CUSTOM && max > 4000) {
|
||||
Toast.notify({ type: 'error', message: t('datasetCreation.stepTwo.maxLengthCheck') })
|
||||
return
|
||||
}
|
||||
setCustomFileIndexingEstimate(null)
|
||||
setShowPreview()
|
||||
fetchFileIndexingEstimate()
|
||||
@ -338,13 +342,17 @@ const StepTwo = ({
|
||||
Toast.notify({ type: 'error', message: t('datasetCreation.stepTwo.overlapCheck') })
|
||||
return
|
||||
}
|
||||
if (segmentationType === SegmentType.CUSTOM && max > 4000) {
|
||||
Toast.notify({ type: 'error', message: t('datasetCreation.stepTwo.maxLengthCheck') })
|
||||
return
|
||||
}
|
||||
if (isSetting) {
|
||||
params = {
|
||||
original_document_id: documentDetail?.id,
|
||||
doc_form: docForm,
|
||||
doc_language: docLanguage,
|
||||
process_rule: getProcessRule(),
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
|
||||
retrieval_model: retrievalConfig, // Readonly. If want to changed, just go to settings page.
|
||||
embedding_model: embeddingModel.model, // Readonly
|
||||
embedding_model_provider: embeddingModel.provider, // Readonly
|
||||
@ -357,7 +365,7 @@ const StepTwo = ({
|
||||
rerankDefaultModel,
|
||||
isRerankDefaultModelValid: !!isRerankDefaultModelValid,
|
||||
rerankModelList,
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
|
||||
retrievalConfig,
|
||||
indexMethod: indexMethod as string,
|
||||
})
|
||||
@ -367,7 +375,7 @@ const StepTwo = ({
|
||||
}
|
||||
const postRetrievalConfig = ensureRerankModelSelected({
|
||||
rerankDefaultModel: rerankDefaultModel!,
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
|
||||
retrievalConfig,
|
||||
indexMethod: indexMethod as string,
|
||||
})
|
||||
@ -646,29 +654,26 @@ const StepTwo = ({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
className={s.input}
|
||||
placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''}
|
||||
value={segmentIdentifier}
|
||||
onChange={e => doSetSegmentIdentifier(e.target.value)}
|
||||
className='h-9'
|
||||
placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={segmentIdentifier}
|
||||
onChange={e => setSegmentIdentifier(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.formRow}>
|
||||
<div className='w-full'>
|
||||
<div className={s.label}>{t('datasetCreation.stepTwo.maxLength')}</div>
|
||||
<div className='relative w-full'>
|
||||
<input
|
||||
type="number"
|
||||
className={s.input}
|
||||
placeholder={t('datasetCreation.stepTwo.maxLength') || ''}
|
||||
value={max}
|
||||
min={1}
|
||||
onChange={e => setMax(parseInt(e.target.value.replace(/^0+/, ''), 10))}
|
||||
/>
|
||||
<div className='absolute top-2.5 right-2.5 text-text-tertiary system-sm-regular'>Tokens</div>
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
className='h-9'
|
||||
placeholder={t('datasetCreation.stepTwo.maxLength') || ''}
|
||||
value={max}
|
||||
max={4000}
|
||||
min={1}
|
||||
onChange={e => setMax(Number.parseInt(e.target.value.replace(/^0+/, ''), 10))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.formRow}>
|
||||
@ -683,17 +688,14 @@ const StepTwo = ({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='relative w-full'>
|
||||
<input
|
||||
type="number"
|
||||
className={s.input}
|
||||
placeholder={t('datasetCreation.stepTwo.overlap') || ''}
|
||||
value={overlap}
|
||||
min={1}
|
||||
onChange={e => setOverlap(parseInt(e.target.value.replace(/^0+/, ''), 10))}
|
||||
/>
|
||||
<div className='absolute top-2.5 right-2.5 text-text-tertiary system-sm-regular'>Tokens</div>
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
className='h-9'
|
||||
placeholder={t('datasetCreation.stepTwo.overlap') || ''}
|
||||
value={overlap}
|
||||
min={1}
|
||||
onChange={e => setOverlap(Number.parseInt(e.target.value.replace(/^0+/, ''), 10))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.formRow}>
|
||||
|
||||
@ -6,7 +6,7 @@ import cn from '@/utils/classnames'
|
||||
import Popover from '@/app/components/base/popover'
|
||||
import { languages } from '@/i18n/language'
|
||||
|
||||
export type ILanguageSelectProps = {
|
||||
export interface ILanguageSelectProps {
|
||||
currentLanguage: string
|
||||
onSelect: (language: string) => void
|
||||
disabled?: boolean
|
||||
@ -24,7 +24,7 @@ const LanguageSelect: FC<ILanguageSelectProps> = ({
|
||||
disabled={disabled}
|
||||
htmlContent={
|
||||
<div className='w-full py-1'>
|
||||
{languages.filter(language => language.supported).map(({ prompt_name, name }) => (
|
||||
{languages.filter(language => language.supported).map(({ prompt_name }) => (
|
||||
<div
|
||||
key={prompt_name}
|
||||
className='py-2 px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer text-gray-700 text-sm'
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
isChecked: boolean
|
||||
onChange: (isChecked: boolean) => void
|
||||
label: string
|
||||
labelClassName?: string
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
const CheckboxWithLabel: FC<Props> = ({
|
||||
className = '',
|
||||
isChecked,
|
||||
onChange,
|
||||
label,
|
||||
labelClassName,
|
||||
tooltip,
|
||||
}) => {
|
||||
return (
|
||||
<label className={cn(className, 'flex items-center h-7 space-x-2')}>
|
||||
<Checkbox checked={isChecked} onCheck={() => onChange(!isChecked)} />
|
||||
<div className={cn(labelClassName, 'text-sm font-normal text-gray-800')}>{label}</div>
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[200px]'>{tooltip}</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4'
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
export default React.memo(CheckboxWithLabel)
|
||||
@ -0,0 +1,30 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
title: string
|
||||
errorMsg?: string
|
||||
}
|
||||
|
||||
const ErrorMessage: FC<Props> = ({
|
||||
className,
|
||||
title,
|
||||
errorMsg,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(className, 'py-2 px-4 border-t border-gray-200 bg-[#FFFAEB]')}>
|
||||
<div className='flex items-center h-5'>
|
||||
<AlertTriangle className='mr-2 w-4 h-4 text-[#F79009]' />
|
||||
<div className='text-sm font-medium text-[#DC6803]'>{title}</div>
|
||||
</div>
|
||||
{errorMsg && (
|
||||
<div className='mt-1 pl-6 leading-[18px] text-xs font-normal text-gray-700'>{errorMsg}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ErrorMessage)
|
||||
@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import Input from './input'
|
||||
import cn from '@/utils/classnames'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
label: string
|
||||
labelClassName?: string
|
||||
value: string | number
|
||||
onChange: (value: string | number) => void
|
||||
isRequired?: boolean
|
||||
placeholder?: string
|
||||
isNumber?: boolean
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
const Field: FC<Props> = ({
|
||||
className,
|
||||
label,
|
||||
labelClassName,
|
||||
value,
|
||||
onChange,
|
||||
isRequired = false,
|
||||
placeholder = '',
|
||||
isNumber = false,
|
||||
tooltip,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(className)}>
|
||||
<div className='flex py-[7px]'>
|
||||
<div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-gray-900')}>{label} </div>
|
||||
{isRequired && <span className='ml-0.5 text-xs font-semibold text-[#D92D20]'>*</span>}
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[200px]'>{tooltip}</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
isNumber={isNumber}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Field)
|
||||
@ -0,0 +1,58 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
interface Props {
|
||||
value: string | number
|
||||
onChange: (value: string | number) => void
|
||||
placeholder?: string
|
||||
isNumber?: boolean
|
||||
}
|
||||
|
||||
const MIN_VALUE = 0
|
||||
|
||||
const Input: FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder = '',
|
||||
isNumber = false,
|
||||
}) => {
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
if (isNumber) {
|
||||
let numberValue = Number.parseInt(value, 10) // integer only
|
||||
if (isNaN(numberValue)) {
|
||||
onChange('')
|
||||
return
|
||||
}
|
||||
if (numberValue < MIN_VALUE)
|
||||
numberValue = MIN_VALUE
|
||||
|
||||
onChange(numberValue)
|
||||
return
|
||||
}
|
||||
onChange(value)
|
||||
}, [isNumber, onChange])
|
||||
|
||||
const otherOption = (() => {
|
||||
if (isNumber) {
|
||||
return {
|
||||
min: MIN_VALUE,
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
}
|
||||
})()
|
||||
return (
|
||||
<input
|
||||
type={isNumber ? 'number' : 'text'}
|
||||
{...otherOption}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
className='flex h-9 w-full py-1 px-2 rounded-lg text-xs leading-normal bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-gray-50 placeholder:text-gray-400'
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(Input)
|
||||
@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
const I18N_PREFIX = 'datasetCreation.stepOne.website'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
controlFoldOptions?: number
|
||||
}
|
||||
|
||||
const OptionsWrap: FC<Props> = ({
|
||||
className = '',
|
||||
children,
|
||||
controlFoldOptions,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [fold, {
|
||||
toggle: foldToggle,
|
||||
setTrue: foldHide,
|
||||
}] = useBoolean(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (controlFoldOptions)
|
||||
foldHide()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [controlFoldOptions])
|
||||
return (
|
||||
<div className={cn(className, !fold ? 'mb-0' : 'mb-3')}>
|
||||
<div
|
||||
className='flex justify-between items-center h-[26px] py-1 cursor-pointer select-none'
|
||||
onClick={foldToggle}
|
||||
>
|
||||
<div className='flex items-center text-gray-700'>
|
||||
<Settings04 className='mr-1 w-4 h-4' />
|
||||
<div className='text-[13px] font-semibold text-gray-800 uppercase'>{t(`${I18N_PREFIX}.options`)}</div>
|
||||
</div>
|
||||
<ChevronRight className={cn(!fold && 'rotate-90', 'w-4 h-4 text-gray-500')} />
|
||||
</div>
|
||||
{!fold && (
|
||||
<div className='mb-4'>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(OptionsWrap)
|
||||
@ -0,0 +1,48 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from './input'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
const I18N_PREFIX = 'datasetCreation.stepOne.website'
|
||||
|
||||
interface Props {
|
||||
isRunning: boolean
|
||||
onRun: (url: string) => void
|
||||
}
|
||||
|
||||
const UrlInput: FC<Props> = ({
|
||||
isRunning,
|
||||
onRun,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [url, setUrl] = useState('')
|
||||
const handleUrlChange = useCallback((url: string | number) => {
|
||||
setUrl(url as string)
|
||||
}, [])
|
||||
const handleOnRun = useCallback(() => {
|
||||
if (isRunning)
|
||||
return
|
||||
onRun(url)
|
||||
}, [isRunning, onRun, url])
|
||||
|
||||
return (
|
||||
<div className='flex items-center justify-between'>
|
||||
<Input
|
||||
value={url}
|
||||
onChange={handleUrlChange}
|
||||
placeholder='https://docs.dify.ai'
|
||||
/>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={handleOnRun}
|
||||
className='ml-2'
|
||||
loading={isRunning}
|
||||
>
|
||||
{!isRunning ? t(`${I18N_PREFIX}.run`) : ''}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(UrlInput)
|
||||
@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
|
||||
interface Props {
|
||||
payload: CrawlResultItemType
|
||||
isChecked: boolean
|
||||
isPreview: boolean
|
||||
onCheckChange: (checked: boolean) => void
|
||||
onPreview: () => void
|
||||
}
|
||||
|
||||
const CrawledResultItem: FC<Props> = ({
|
||||
isPreview,
|
||||
payload,
|
||||
isChecked,
|
||||
onCheckChange,
|
||||
onPreview,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleCheckChange = useCallback(() => {
|
||||
onCheckChange(!isChecked)
|
||||
}, [isChecked, onCheckChange])
|
||||
return (
|
||||
<div className={cn(isPreview ? 'border-[#D1E0FF] bg-primary-50 shadow-xs' : 'group hover:bg-gray-100', 'rounded-md px-2 py-[5px] cursor-pointer border border-transparent')}>
|
||||
<div className='flex items-center h-5'>
|
||||
<Checkbox className='group-hover:border-2 group-hover:border-primary-600 mr-2 shrink-0' checked={isChecked} onCheck={handleCheckChange} />
|
||||
<div className='grow w-0 truncate text-sm font-medium text-gray-700' title={payload.title}>{payload.title}</div>
|
||||
<div onClick={onPreview} className='hidden group-hover:flex items-center h-6 px-2 text-xs rounded-md font-medium text-gray-500 uppercase hover:bg-gray-50'>{t('datasetCreation.stepOne.website.preview')}</div>
|
||||
</div>
|
||||
<div className='mt-0.5 truncate pl-6 leading-[18px] text-xs font-normal text-gray-500' title={payload.source_url}>{payload.source_url}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(CrawledResultItem)
|
||||
@ -0,0 +1,87 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CheckboxWithLabel from './base/checkbox-with-label'
|
||||
import CrawledResultItem from './crawled-result-item'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
|
||||
const I18N_PREFIX = 'datasetCreation.stepOne.website'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
list: CrawlResultItem[]
|
||||
checkedList: CrawlResultItem[]
|
||||
onSelectedChange: (selected: CrawlResultItem[]) => void
|
||||
onPreview: (payload: CrawlResultItem) => void
|
||||
usedTime: number
|
||||
}
|
||||
|
||||
const CrawledResult: FC<Props> = ({
|
||||
className = '',
|
||||
list,
|
||||
checkedList,
|
||||
onSelectedChange,
|
||||
onPreview,
|
||||
usedTime,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const isCheckAll = checkedList.length === list.length
|
||||
|
||||
const handleCheckedAll = useCallback(() => {
|
||||
if (!isCheckAll)
|
||||
onSelectedChange(list)
|
||||
|
||||
else
|
||||
onSelectedChange([])
|
||||
}, [isCheckAll, list, onSelectedChange])
|
||||
|
||||
const handleItemCheckChange = useCallback((item: CrawlResultItem) => {
|
||||
return (checked: boolean) => {
|
||||
if (checked)
|
||||
onSelectedChange([...checkedList, item])
|
||||
|
||||
else
|
||||
onSelectedChange(checkedList.filter(checkedItem => checkedItem.source_url !== item.source_url))
|
||||
}
|
||||
}, [checkedList, onSelectedChange])
|
||||
|
||||
const [previewIndex, setPreviewIndex] = React.useState<number>(-1)
|
||||
const handlePreview = useCallback((index: number) => {
|
||||
return () => {
|
||||
setPreviewIndex(index)
|
||||
onPreview(list[index])
|
||||
}
|
||||
}, [list, onPreview])
|
||||
|
||||
return (
|
||||
<div className={cn(className, 'border-t border-gray-200')}>
|
||||
<div className='flex items-center justify-between h-[34px] px-4 bg-gray-50 shadow-xs border-b-[0.5px] border-black/8 text-xs font-normal text-gray-700'>
|
||||
<CheckboxWithLabel
|
||||
isChecked={isCheckAll}
|
||||
onChange={handleCheckedAll} label={isCheckAll ? t(`${I18N_PREFIX}.resetAll`) : t(`${I18N_PREFIX}.selectAll`)}
|
||||
labelClassName='!font-medium'
|
||||
/>
|
||||
<div>{t(`${I18N_PREFIX}.scrapTimeInfo`, {
|
||||
total: list.length,
|
||||
time: usedTime.toFixed(1),
|
||||
})}</div>
|
||||
</div>
|
||||
<div className='p-2'>
|
||||
{list.map((item, index) => (
|
||||
<CrawledResultItem
|
||||
key={item.source_url}
|
||||
isPreview={index === previewIndex}
|
||||
onPreview={handlePreview(index)}
|
||||
payload={item}
|
||||
isChecked={checkedList.some(checkedItem => checkedItem.source_url === item.source_url)}
|
||||
onCheckChange={handleItemCheckChange(item)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(CrawledResult)
|
||||
@ -0,0 +1,37 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RowStruct } from '@/app/components/base/icons/src/public/other'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
crawledNum: number
|
||||
totalNum: number
|
||||
}
|
||||
|
||||
const Crawling: FC<Props> = ({
|
||||
className = '',
|
||||
crawledNum,
|
||||
totalNum,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className={cn(className, 'border-t border-gray-200')}>
|
||||
<div className='flex items-center h-[34px] px-4 bg-gray-50 shadow-xs border-b-[0.5px] border-black/8 text-xs font-normal text-gray-700'>
|
||||
{t('datasetCreation.stepOne.website.totalPageScraped')} {crawledNum}/{totalNum}
|
||||
</div>
|
||||
|
||||
<div className='p-2'>
|
||||
{['', '', '', ''].map((item, index) => (
|
||||
<div className='py-[5px]' key={index}>
|
||||
<RowStruct />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Crawling)
|
||||
@ -0,0 +1,24 @@
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
|
||||
const result: CrawlResultItem[] = [
|
||||
{
|
||||
title: 'Start the frontend Docker container separately',
|
||||
markdown: 'Markdown 1',
|
||||
description: 'Description 1',
|
||||
source_url: 'https://example.com/1',
|
||||
},
|
||||
{
|
||||
title: 'Advanced Tool Integration',
|
||||
markdown: 'Markdown 2',
|
||||
description: 'Description 2',
|
||||
source_url: 'https://example.com/2',
|
||||
},
|
||||
{
|
||||
title: 'Local Source Code Start | English | Dify',
|
||||
markdown: 'Markdown 3',
|
||||
description: 'Description 3',
|
||||
source_url: 'https://example.com/3',
|
||||
},
|
||||
]
|
||||
|
||||
export default result
|
||||
@ -1,10 +1,11 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { memo, useEffect, useMemo, useState } from 'react'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { HashtagIcon } from '@heroicons/react/24/solid'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { debounce, isNil, omitBy } from 'lodash-es'
|
||||
import { isNil, omitBy } from 'lodash-es'
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiEditLine,
|
||||
@ -48,7 +49,7 @@ export const SegmentIndexTag: FC<{ positionId: string | number; className?: stri
|
||||
)
|
||||
}
|
||||
|
||||
type ISegmentDetailProps = {
|
||||
interface ISegmentDetailProps {
|
||||
embeddingAvailable: boolean
|
||||
segInfo?: Partial<SegmentDetailModel> & { id: string }
|
||||
onChangeSwitch?: (segId: string, enabled: boolean) => Promise<void>
|
||||
@ -216,7 +217,7 @@ export const splitArray = (arr: any[], size = 3) => {
|
||||
return result
|
||||
}
|
||||
|
||||
type ICompletedProps = {
|
||||
interface ICompletedProps {
|
||||
embeddingAvailable: boolean
|
||||
showNewSegmentModal: boolean
|
||||
onNewSegmentModalChange: (state: boolean) => void
|
||||
@ -241,7 +242,8 @@ const Completed: FC<ICompletedProps> = ({
|
||||
// the current segment id and whether to show the modal
|
||||
const [currSegment, setCurrSegment] = useState<{ segInfo?: SegmentDetailModel; showModal: boolean }>({ showModal: false })
|
||||
|
||||
const [searchValue, setSearchValue] = useState<string>() // the search value
|
||||
const [inputValue, setInputValue] = useState<string>('') // the input value
|
||||
const [searchValue, setSearchValue] = useState<string>('') // the search value
|
||||
const [selectedStatus, setSelectedStatus] = useState<boolean | 'all'>('all') // the selected status, enabled/disabled/undefined
|
||||
|
||||
const [lastSegmentsRes, setLastSegmentsRes] = useState<SegmentsResponse | undefined>(undefined)
|
||||
@ -250,6 +252,15 @@ const Completed: FC<ICompletedProps> = ({
|
||||
const [total, setTotal] = useState<number | undefined>()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
const { run: handleSearch } = useDebounceFn(() => {
|
||||
setSearchValue(inputValue)
|
||||
}, { wait: 500 })
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
setInputValue(value)
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const onChangeStatus = ({ value }: Item) => {
|
||||
setSelectedStatus(value === 'all' ? 'all' : !!value)
|
||||
}
|
||||
@ -391,7 +402,14 @@ const Completed: FC<ICompletedProps> = ({
|
||||
defaultValue={'all'}
|
||||
className={s.select}
|
||||
wrapperClassName='h-fit w-[120px] mr-2' />
|
||||
<Input showLeftIcon wrapperClassName='!w-52' className='!h-8' onChange={debounce(e => setSearchValue(e.target.value), 500)} />
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
wrapperClassName='!w-52'
|
||||
value={inputValue}
|
||||
onChange={e => handleInputChange(e.target.value)}
|
||||
onClear={() => handleInputChange('')}
|
||||
/>
|
||||
</div>
|
||||
<InfiniteVirtualList
|
||||
embeddingAvailable={embeddingAvailable}
|
||||
|
||||
@ -29,7 +29,7 @@ const map2Options = (map: { [key: string]: string }) => {
|
||||
return Object.keys(map).map(key => ({ value: key, name: map[key] }))
|
||||
}
|
||||
|
||||
type IFieldInfoProps = {
|
||||
interface IFieldInfoProps {
|
||||
label: string
|
||||
value?: string
|
||||
displayedValue?: string
|
||||
@ -78,7 +78,6 @@ export const FieldInfo: FC<IFieldInfoProps> = ({
|
||||
placeholder={`${t('datasetDocuments.metadata.placeholder.add')}${label}`}
|
||||
/>
|
||||
: <Input
|
||||
className={s.input}
|
||||
onChange={e => onUpdate?.(e.target.value)}
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
@ -115,7 +114,7 @@ const IconButton: FC<{
|
||||
)
|
||||
}
|
||||
|
||||
type IMetadataProps = {
|
||||
interface IMetadataProps {
|
||||
docDetail?: FullDocumentDetail
|
||||
loading: boolean
|
||||
onUpdate: () => void
|
||||
|
||||
@ -5,7 +5,7 @@ import { useBoolean } from 'ahooks'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import type { FullDocumentDetail } from '@/models/datasets'
|
||||
import type { CrawlOptions, CustomFile, FullDocumentDetail } from '@/models/datasets'
|
||||
import type { MetadataType } from '@/service/datasets'
|
||||
import { fetchDocumentDetail } from '@/service/datasets'
|
||||
|
||||
@ -15,8 +15,9 @@ import AccountSetting from '@/app/components/header/account-setting'
|
||||
import AppUnavailable from '@/app/components/base/app-unavailable'
|
||||
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { NotionPage } from '@/models/common'
|
||||
|
||||
type DocumentSettingsProps = {
|
||||
interface DocumentSettingsProps {
|
||||
datasetId: string
|
||||
documentId: string
|
||||
}
|
||||
@ -40,7 +41,7 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
|
||||
page_id: documentDetail?.data_source_info.notion_page_id,
|
||||
page_name: documentDetail?.name,
|
||||
page_icon: documentDetail?.data_source_info.notion_page_icon,
|
||||
type: documentDetail?.data_source_info.type,
|
||||
type: documentDetail?.data_source_type,
|
||||
}
|
||||
}, [documentDetail])
|
||||
useEffect(() => {
|
||||
@ -72,7 +73,7 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
|
||||
onSetting={showSetAPIKey}
|
||||
datasetId={datasetId}
|
||||
dataSourceType={documentDetail.data_source_type}
|
||||
notionPages={[currentPage]}
|
||||
notionPages={[currentPage as unknown as NotionPage]}
|
||||
websitePages={[
|
||||
{
|
||||
title: documentDetail.name,
|
||||
@ -81,12 +82,13 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
|
||||
description: '',
|
||||
},
|
||||
]}
|
||||
fireCrawlJobId={documentDetail.data_source_info?.job_id}
|
||||
crawlOptions={documentDetail.data_source_info}
|
||||
websiteCrawlProvider={documentDetail.data_source_info?.provider}
|
||||
websiteCrawlJobId={documentDetail.data_source_info?.job_id}
|
||||
crawlOptions={documentDetail.data_source_info as unknown as CrawlOptions}
|
||||
indexingType={indexingTechnique || ''}
|
||||
isSetting
|
||||
documentDetail={documentDetail}
|
||||
files={[documentDetail.data_source_info.upload_file]}
|
||||
files={[documentDetail.data_source_info.upload_file as CustomFile]}
|
||||
onSave={saveHandler}
|
||||
onCancel={cancelHandler}
|
||||
/>
|
||||
|
||||
@ -4,9 +4,9 @@ import React, { useMemo, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useDebounce, useDebounceFn } from 'ahooks'
|
||||
import { groupBy, omit } from 'lodash-es'
|
||||
import { PlusIcon } from '@heroicons/react/24/solid'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import List from './list'
|
||||
import s from './style.module.css'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -69,7 +69,7 @@ const EmptyElement: FC<{ canAdd: boolean; onClick: () => void; type?: 'upload' |
|
||||
</div>
|
||||
}
|
||||
|
||||
type IDocumentsProps = {
|
||||
interface IDocumentsProps {
|
||||
datasetId: string
|
||||
}
|
||||
|
||||
@ -77,6 +77,7 @@ export const fetcher = (url: string) => get(url, {}, {})
|
||||
|
||||
const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
|
||||
const { t } = useTranslation()
|
||||
const [inputValue, setInputValue] = useState<string>('') // the input value
|
||||
const [searchValue, setSearchValue] = useState<string>('')
|
||||
const [currPage, setCurrPage] = React.useState<number>(0)
|
||||
const router = useRouter()
|
||||
@ -195,6 +196,15 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
|
||||
|
||||
const documentsList = isDataSourceNotion ? documentsWithProgress?.data : documentsRes?.data
|
||||
|
||||
const { run: handleSearch } = useDebounceFn(() => {
|
||||
setSearchValue(inputValue)
|
||||
}, { wait: 500 })
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
setInputValue(value)
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col h-full overflow-y-auto'>
|
||||
<div className='flex flex-col justify-center gap-1 px-6 pt-4'>
|
||||
@ -205,10 +215,11 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
|
||||
<div className='flex items-center justify-between flex-wrap'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
wrapperClassName='!w-[200px]'
|
||||
className='!h-8 !text-[13px]'
|
||||
onChange={e => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
value={inputValue}
|
||||
onChange={e => handleInputChange(e.target.value)}
|
||||
onClear={() => handleInputChange('')}
|
||||
/>
|
||||
<div className='flex gap-2 justify-center items-center !h-8'>
|
||||
<RetryButton datasetId={datasetId} />
|
||||
|
||||
@ -6,9 +6,10 @@ import { useBoolean } from 'ahooks'
|
||||
import Toast from '../../base/toast'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { renameDocumentName } from '@/service/datasets'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
datasetId: string
|
||||
documentId: string
|
||||
name: string
|
||||
@ -59,7 +60,8 @@ const RenameModal: FC<Props> = ({
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={'mt-6 font-medium text-sm leading-[21px] text-gray-900'}>{t('datasetDocuments.list.table.name')}</div>
|
||||
<input className={'mt-2 w-full rounded-lg h-10 box-border px-3 text-sm leading-10 bg-gray-100'}
|
||||
<Input
|
||||
className='mt-2 h-10'
|
||||
value={newName}
|
||||
onChange={e => setNewName(e.target.value)}
|
||||
/>
|
||||
|
||||
@ -19,10 +19,6 @@
|
||||
.desc {
|
||||
@apply text-sm font-normal text-gray-500;
|
||||
}
|
||||
.textarea {
|
||||
height: 220px;
|
||||
@apply border-none resize-none font-normal caret-primary-600 text-gray-700 text-sm w-full focus-visible:outline-none placeholder:text-gray-300 placeholder:text-sm placeholder:font-normal !important;
|
||||
}
|
||||
.table {
|
||||
@apply text-[13px] text-gray-500;
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ import { externalKnowledgeBaseHitTesting, hitTesting } from '@/service/datasets'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
|
||||
|
||||
type TextAreaWithButtonIProps = {
|
||||
interface TextAreaWithButtonIProps {
|
||||
datasetId: string
|
||||
onUpdateList: () => void
|
||||
setHitResult: (res: HitTestingResponse) => void
|
||||
@ -151,10 +151,10 @@ const TextAreaWithButton = ({
|
||||
</div>
|
||||
<div className='px-4 pb-11'>
|
||||
<textarea
|
||||
className='h-[220px] border-none resize-none font-normal caret-primary-600 text-gray-700 text-sm w-full focus-visible:outline-none placeholder:text-gray-300 placeholder:text-sm placeholder:font-normal'
|
||||
value={text}
|
||||
onChange={handleTextChange}
|
||||
placeholder={t('datasetHitTesting.input.placeholder') as string}
|
||||
className={s.textarea}
|
||||
/>
|
||||
<div className="absolute inset-x-0 bottom-0 flex items-center justify-between mx-4 mt-2 mb-2">
|
||||
{text?.length > 200
|
||||
|
||||
@ -7,12 +7,14 @@ import { useContext } from 'use-context-selector'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
|
||||
type RenameDatasetModalProps = {
|
||||
interface RenameDatasetModalProps {
|
||||
show: boolean
|
||||
dataset: DataSet
|
||||
onSuccess?: () => void
|
||||
@ -75,10 +77,10 @@ const RenameDatasetModal = ({ show, dataset, onSuccess, onClose }: RenameDataset
|
||||
<div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-gray-900'>
|
||||
{t('datasetSettings.form.name')}
|
||||
</div>
|
||||
<input
|
||||
<Input
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
className='block px-3 w-full h-9 bg-gray-100 rounded-lg text-sm text-gray-900 outline-none appearance-none'
|
||||
className='h-9'
|
||||
placeholder={t('datasetSettings.form.namePlaceholder') || ''}
|
||||
/>
|
||||
</div>
|
||||
@ -87,10 +89,10 @@ const RenameDatasetModal = ({ show, dataset, onSuccess, onClose }: RenameDataset
|
||||
{t('datasetSettings.form.desc')}
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<textarea
|
||||
<Textarea
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
className='block px-3 py-2 w-full h-[88px] rounded-lg bg-gray-100 text-sm outline-none appearance-none resize-none'
|
||||
className='resize-none'
|
||||
placeholder={t('datasetSettings.form.descPlaceholder') || ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -8,11 +8,12 @@ import { unstable_serialize } from 'swr/infinite'
|
||||
import PermissionSelector from '../permission-selector'
|
||||
import IndexMethodRadio from '../index-method-radio'
|
||||
import RetrievalSettings from '../../external-knowledge-base/create/RetrievalSettings'
|
||||
import cn from '@/utils/classnames'
|
||||
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 Divider from '@/app/components/base/divider'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
@ -37,9 +38,6 @@ const rowClass = `
|
||||
const labelClass = `
|
||||
flex items-center w-[168px] h-9
|
||||
`
|
||||
const inputClass = `
|
||||
w-full max-w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none
|
||||
`
|
||||
|
||||
const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
|
||||
if (!pageIndex || previousPageData.has_more)
|
||||
@ -187,9 +185,9 @@ const Form = () => {
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.name')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<input
|
||||
<Input
|
||||
disabled={!currentDataset?.embedding_available}
|
||||
className={cn(inputClass, !currentDataset?.embedding_available && 'opacity-60', 'h-9')}
|
||||
className='h-9'
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
/>
|
||||
@ -200,9 +198,9 @@ const Form = () => {
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.desc')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<textarea
|
||||
<Textarea
|
||||
disabled={!currentDataset?.embedding_available}
|
||||
className={cn(`${inputClass} block mb-2 h-[120px] py-2 resize-none`, !currentDataset?.embedding_available && 'opacity-60')}
|
||||
className='mb-2 h-[120px] resize-none'
|
||||
placeholder={t('datasetSettings.form.descPlaceholder') || ''}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
@ -261,7 +259,7 @@ const Form = () => {
|
||||
{/* Retrieval Method Config */}
|
||||
{currentDataset?.provider === 'external'
|
||||
? <>
|
||||
<div className={rowClass}><Divider/></div>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||
@ -274,7 +272,7 @@ const Form = () => {
|
||||
isInRetrievalSetting={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={rowClass}><Divider/></div>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.externalKnowledgeAPI')}</div>
|
||||
@ -300,7 +298,7 @@ const Form = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={rowClass}><Divider/></div>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
</>
|
||||
: <div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
|
||||
@ -9,13 +9,13 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Avatar from '@/app/components/base/avatar'
|
||||
import SearchInput from '@/app/components/base/search-input'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { Check } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Users01, UsersPlus } from '@/app/components/base/icons/src/vender/solid/users'
|
||||
import type { DatasetPermission } from '@/models/datasets'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import type { Member } from '@/models/common'
|
||||
export type RoleSelectorProps = {
|
||||
export interface RoleSelectorProps {
|
||||
disabled?: boolean
|
||||
permission?: DatasetPermission
|
||||
value: string[]
|
||||
@ -99,7 +99,7 @@ const PermissionSelector = ({ disabled, permission, value, memberList, onChange,
|
||||
)}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1002]'>
|
||||
<div className='relative w-[480px] bg-white rounded-lg border-[0.5px] bg-gray-200 shadow-lg'>
|
||||
<div className='relative w-[480px] rounded-lg border-[0.5px] bg-white shadow-lg'>
|
||||
<div className='p-1'>
|
||||
<div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
|
||||
onChange('only_me')
|
||||
@ -139,7 +139,13 @@ const PermissionSelector = ({ disabled, permission, value, memberList, onChange,
|
||||
{permission === 'partial_members' && (
|
||||
<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'>
|
||||
<SearchInput white value={keywords} onChange={handleKeywordsChange} />
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
value={keywords}
|
||||
onChange={e => handleKeywordsChange(e.target.value)}
|
||||
onClear={() => handleKeywordsChange('')}
|
||||
/>
|
||||
</div>
|
||||
{showMe && (
|
||||
<div className='pl-3 pr-[10px] py-1 flex gap-2 items-center rounded-lg'>
|
||||
|
||||
Reference in New Issue
Block a user