mirror of https://github.com/langgenius/dify.git
feat: enhance dataset creation UI with new pipeline list and edit functionality
This commit is contained in:
parent
7ed398267f
commit
14ad34af71
|
|
@ -19,20 +19,21 @@ export type AppIconProps = {
|
|||
imageUrl?: string | null
|
||||
className?: string
|
||||
innerIcon?: React.ReactNode
|
||||
showEditIcon?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
const appIconVariants = cva(
|
||||
'flex items-center justify-center relative text-lg rounded-2xl grow-0 shrink-0 overflow-hidden leading-none border-[0.5px] border-divider-regular',
|
||||
'flex items-center justify-center relative grow-0 shrink-0 overflow-hidden leading-none border-[0.5px] border-divider-regular',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: 'w-4 h-4 text-xs',
|
||||
tiny: 'w-6 h-6 text-base',
|
||||
small: 'w-8 h-8 text-xl',
|
||||
medium: 'w-9 h-9 text-[22px]',
|
||||
large: 'w-10 h-10 text-[24px]',
|
||||
xl: 'w-12 h-12 text-[28px]',
|
||||
xxl: 'w-14 h-14 text-[32px]',
|
||||
xs: 'w-4 h-4 text-xs rounded-[4px]',
|
||||
tiny: 'w-6 h-6 text-base rounded-md',
|
||||
small: 'w-8 h-8 text-xl rounded-lg',
|
||||
medium: 'w-9 h-9 text-[22px] rounded-[10px]',
|
||||
large: 'w-10 h-10 text-[24px] rounded-[10px]',
|
||||
xl: 'w-12 h-12 text-[28px] rounded-xl',
|
||||
xxl: 'w-14 h-14 text-[32px] rounded-2xl',
|
||||
},
|
||||
rounded: {
|
||||
true: 'rounded-full',
|
||||
|
|
@ -43,6 +44,46 @@ const appIconVariants = cva(
|
|||
rounded: false,
|
||||
},
|
||||
})
|
||||
const EditIconWrapperVariants = cva(
|
||||
'absolute left-0 top-0 z-10 flex items-center justify-center bg-background-overlay-alt',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: 'w-4 h-4 rounded-[4px]',
|
||||
tiny: 'w-6 h-6 rounded-md',
|
||||
small: 'w-8 h-8 rounded-lg',
|
||||
medium: 'w-9 h-9 rounded-[10px]',
|
||||
large: 'w-10 h-10 rounded-[10px]',
|
||||
xl: 'w-12 h-12 rounded-xl',
|
||||
xxl: 'w-14 h-14 rounded-2xl',
|
||||
},
|
||||
rounded: {
|
||||
true: 'rounded-full',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'medium',
|
||||
rounded: false,
|
||||
},
|
||||
})
|
||||
const EditIconVariants = cva(
|
||||
'text-text-primary-on-surface',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: 'size-3',
|
||||
tiny: 'size-3.5',
|
||||
small: 'size-5',
|
||||
medium: 'size-[22px]',
|
||||
large: 'size-6',
|
||||
xl: 'size-7',
|
||||
xxl: 'size-8',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'medium',
|
||||
},
|
||||
})
|
||||
const AppIcon: FC<AppIconProps> = ({
|
||||
size = 'medium',
|
||||
rounded = false,
|
||||
|
|
@ -53,6 +94,7 @@ const AppIcon: FC<AppIconProps> = ({
|
|||
className,
|
||||
innerIcon,
|
||||
onClick,
|
||||
showEditIcon = false,
|
||||
}) => {
|
||||
const isValidImageIcon = iconType === 'image' && imageUrl
|
||||
const Icon = (icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />
|
||||
|
|
@ -72,9 +114,9 @@ const AppIcon: FC<AppIconProps> = ({
|
|||
: (innerIcon || Icon)
|
||||
}
|
||||
{
|
||||
isHovering && (
|
||||
<div className='absolute left-0 top-0 z-10 flex size-14 items-center justify-center rounded-2xl bg-background-overlay-alt'>
|
||||
<RiEditLine className='size-6 text-text-primary-on-surface' />
|
||||
showEditIcon && isHovering && (
|
||||
<div className={EditIconWrapperVariants({ size, rounded })}>
|
||||
<RiEditLine className={EditIconVariants({ size })} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ const CreateFromScratch = ({
|
|||
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
||||
background={appIcon.type === 'image' ? undefined : appIcon.background}
|
||||
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
||||
showEditIcon
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import HeaderEffect from './header-effect'
|
||||
import Header from './header'
|
||||
import CreateOptions from './create-options'
|
||||
import List from './list'
|
||||
|
||||
const CreateFromPipeline = () => {
|
||||
return (
|
||||
|
|
@ -12,6 +13,7 @@ const CreateFromPipeline = () => {
|
|||
<HeaderEffect />
|
||||
<Header />
|
||||
<CreateOptions />
|
||||
<List />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { ChunkingMode } from '@/models/datasets'
|
||||
import TemplateCard from './template-card'
|
||||
|
||||
export type Pipeline = {
|
||||
id: string
|
||||
name: string
|
||||
icon_type: 'emoji' | 'image'
|
||||
icon?: string
|
||||
icon_background?: string
|
||||
file_id?: string
|
||||
url?: string
|
||||
description: string
|
||||
doc_form: ChunkingMode
|
||||
}
|
||||
|
||||
const BuiltInPipelineList = () => {
|
||||
const mockData: Pipeline[] = [{
|
||||
id: '1',
|
||||
name: 'Pipeline 1',
|
||||
description: 'This is a description of Pipeline 1. When use the general chunking mode, the chunks retrieved and recalled are the same. When use the general chunking mode, the chunks retrieved and recalled are the same.',
|
||||
icon_type: 'emoji',
|
||||
icon: '🤖',
|
||||
icon_background: '#F0FDF9',
|
||||
doc_form: ChunkingMode.text,
|
||||
}, {
|
||||
id: '2',
|
||||
name: 'Pipeline 2',
|
||||
description: 'This is a description of Pipeline 2. When use the general chunking mode, the chunks retrieved and recalled are the same.',
|
||||
icon_type: 'emoji',
|
||||
icon: '🏖️',
|
||||
icon_background: '#FFF4ED',
|
||||
doc_form: ChunkingMode.parentChild,
|
||||
}, {
|
||||
id: '3',
|
||||
name: 'Pipeline 3',
|
||||
description: 'This is a description of Pipeline 3',
|
||||
icon_type: 'emoji',
|
||||
icon: '🚀',
|
||||
icon_background: '#FEFBE8',
|
||||
doc_form: ChunkingMode.qa,
|
||||
}, {
|
||||
id: '4',
|
||||
name: 'Pipeline 4',
|
||||
description: 'This is a description of Pipeline 4',
|
||||
icon_type: 'emoji',
|
||||
icon: '🍯',
|
||||
icon_background: '#F5F3FF',
|
||||
doc_form: ChunkingMode.graph,
|
||||
}]
|
||||
|
||||
return (
|
||||
<div className='grid grow grid-cols-1 gap-3 overflow-y-auto px-16 pt-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
||||
{mockData.map((pipeline, index) => (
|
||||
<TemplateCard
|
||||
key={index}
|
||||
pipeline={pipeline}
|
||||
showMoreOperations={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BuiltInPipelineList
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { ChunkingMode } from '@/models/datasets'
|
||||
import type { Pipeline } from './built-in-pipeline-list'
|
||||
import TemplateCard from './template-card'
|
||||
|
||||
const CustomizedList = () => {
|
||||
const mockData: Pipeline[] = [{
|
||||
id: '1',
|
||||
name: 'Pipeline 1',
|
||||
description: 'This is a description of Pipeline 1. When use the general chunking mode, the chunks retrieved and recalled are the same. When use the general chunking mode, the chunks retrieved and recalled are the same.',
|
||||
icon_type: 'emoji',
|
||||
icon: '🤖',
|
||||
icon_background: '#F0FDF9',
|
||||
doc_form: ChunkingMode.text,
|
||||
}, {
|
||||
id: '2',
|
||||
name: 'Pipeline 2',
|
||||
description: 'This is a description of Pipeline 2. When use the general chunking mode, the chunks retrieved and recalled are the same.',
|
||||
icon_type: 'emoji',
|
||||
icon: '🏖️',
|
||||
icon_background: '#FFF4ED',
|
||||
doc_form: ChunkingMode.parentChild,
|
||||
}, {
|
||||
id: '3',
|
||||
name: 'Pipeline 3',
|
||||
description: 'This is a description of Pipeline 3',
|
||||
icon_type: 'emoji',
|
||||
icon: '🚀',
|
||||
icon_background: '#FEFBE8',
|
||||
doc_form: ChunkingMode.qa,
|
||||
}, {
|
||||
id: '4',
|
||||
name: 'Pipeline 4',
|
||||
description: 'This is a description of Pipeline 4',
|
||||
icon_type: 'emoji',
|
||||
icon: '🍯',
|
||||
icon_background: '#F5F3FF',
|
||||
doc_form: ChunkingMode.graph,
|
||||
}]
|
||||
|
||||
return (
|
||||
<div className='grid grow grid-cols-1 gap-3 overflow-y-auto px-16 pt-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
||||
{mockData.map((pipeline, index) => (
|
||||
<TemplateCard
|
||||
key={index}
|
||||
pipeline={pipeline}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomizedList
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import Tab from './tab'
|
||||
import BuiltInPipelineList from './built-in-pipeline-list'
|
||||
import CustomizedList from './customized-list'
|
||||
|
||||
const OPTIONS = [
|
||||
{ value: 'built-in', label: 'Built-in Pipeline' },
|
||||
{ value: 'customized', label: 'Customized' },
|
||||
]
|
||||
|
||||
const List = () => {
|
||||
const [activeTab, setActiveTab] = useState('built-in')
|
||||
|
||||
const handleTabChange = useCallback((tab: string) => {
|
||||
setActiveTab(tab)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='flex grow flex-col'>
|
||||
<Tab
|
||||
activeTab={activeTab}
|
||||
handleTabChange={handleTabChange}
|
||||
options={OPTIONS}
|
||||
/>
|
||||
{
|
||||
activeTab === 'built-in' && <BuiltInPipelineList />
|
||||
}
|
||||
{
|
||||
activeTab === 'customized' && <CustomizedList />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default List
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react'
|
||||
import Item from './item'
|
||||
|
||||
type TabProps = {
|
||||
activeTab: string
|
||||
handleTabChange: (tab: string) => void
|
||||
options: { value: string; label: string; }[]
|
||||
}
|
||||
|
||||
const Tab = ({
|
||||
activeTab,
|
||||
handleTabChange,
|
||||
options,
|
||||
}: TabProps) => {
|
||||
return (
|
||||
<div className='px-16 pt-2'>
|
||||
<div className='relative flex h-10 items-center gap-x-6'>
|
||||
{options.map((option, index) => (
|
||||
<Item
|
||||
key={index}
|
||||
option={option}
|
||||
isSelected={activeTab === option.value}
|
||||
onClick={handleTabChange}
|
||||
/>
|
||||
))}
|
||||
<div className='absolute bottom-0 left-0 h-px w-full bg-divider-subtle' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Tab)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import cn from '@/utils/classnames'
|
||||
import React from 'react'
|
||||
|
||||
type ItemProps = {
|
||||
isSelected: boolean
|
||||
option: { value: string; label: string }
|
||||
onClick: (value: string) => void
|
||||
}
|
||||
|
||||
const Item = ({
|
||||
isSelected,
|
||||
option,
|
||||
onClick,
|
||||
}: ItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'system-sm-semibold-uppercase relative flex h-full cursor-pointer items-center',
|
||||
isSelected ? 'text-text-primary' : 'text-text-tertiary',
|
||||
)}
|
||||
onClick={onClick.bind(null, option.value)}
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
{isSelected && <div className='absolute bottom-0 left-0 h-0.5 w-full bg-util-colors-blue-brand-blue-brand-600' />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Item)
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
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 React, { useCallback, useRef, useState } from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { Pipeline } from '../built-in-pipeline-list'
|
||||
|
||||
type EditPipelineInfoProps = {
|
||||
onClose: () => void
|
||||
onSave: () => void
|
||||
pipeline: Pipeline
|
||||
}
|
||||
|
||||
const EditPipelineInfo = ({
|
||||
onClose,
|
||||
onSave,
|
||||
pipeline,
|
||||
}: EditPipelineInfoProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [name, setName] = useState(pipeline.name)
|
||||
const [appIcon, setAppIcon] = useState<AppIconSelection>(
|
||||
pipeline.icon_type === 'image'
|
||||
? { type: 'image' as const, url: pipeline.url || '', fileId: pipeline.file_id || '' }
|
||||
: { type: 'emoji' as const, icon: pipeline.icon || '', background: pipeline.icon_background || '' },
|
||||
)
|
||||
const [description, setDescription] = useState(pipeline.description)
|
||||
const [showAppIconPicker, setShowAppIconPicker] = useState(false)
|
||||
const previousAppIcon = useRef<AppIconSelection>(
|
||||
pipeline.icon_type === 'image'
|
||||
? { type: 'image' as const, url: pipeline.url || '', fileId: pipeline.file_id || '' }
|
||||
: { type: 'emoji' as const, icon: pipeline.icon || '', background: pipeline.icon_background || '' },
|
||||
)
|
||||
|
||||
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 handleSave = useCallback(() => {
|
||||
if (!name) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: 'Please enter a name for the Knowledge Base.',
|
||||
})
|
||||
return
|
||||
}
|
||||
onSave()
|
||||
onClose()
|
||||
}, [name, onSave, onClose])
|
||||
|
||||
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'>
|
||||
Edit Pipeline Info
|
||||
</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'>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>Pipeline name & icon</label>
|
||||
<Input
|
||||
onChange={handleAppNameChange}
|
||||
value={name}
|
||||
placeholder='Please enter the name of the Knowledge Base'
|
||||
/>
|
||||
</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}
|
||||
showEditIcon
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>Knowledge description</label>
|
||||
<Textarea
|
||||
onChange={handleDescriptionChange}
|
||||
value={description}
|
||||
placeholder='Describe what is in this Knowledge Base. A detailed description allows AI to access the content of the dataset more accurately. If empty, Dify will use the default hit strategy. (Optional)'
|
||||
/>
|
||||
</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={handleSave}
|
||||
>
|
||||
{t('common.operation.save')}
|
||||
</Button>
|
||||
</div>
|
||||
{showAppIconPicker && (
|
||||
<AppIconPicker
|
||||
onSelect={handleSelectAppIcon}
|
||||
onClose={handleCloseAppIconPicker}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(EditPipelineInfo)
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
import React, { useCallback, useState } from 'react'
|
||||
import type { Pipeline } from '../built-in-pipeline-list'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { DOC_FORM_ICON, DOC_FORM_TEXT } from '../../../list/dataset-card'
|
||||
import { General } from '@/app/components/base/icons/src/public/knowledge'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiAddLine, RiArrowRightUpLine, RiMoreFill } from '@remixicon/react'
|
||||
import CustomPopover from '@/app/components/base/popover'
|
||||
import Operations from './operations'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import EditPipelineInfo from './edit-pipeline-info'
|
||||
|
||||
type TemplateCardProps = {
|
||||
pipeline: Pipeline
|
||||
showMoreOperations?: boolean
|
||||
}
|
||||
|
||||
const TemplateCard = ({
|
||||
pipeline,
|
||||
showMoreOperations = true,
|
||||
}: TemplateCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [showEditModal, setShowEditModal] = useState(false)
|
||||
|
||||
const openEditModal = useCallback(() => {
|
||||
setShowEditModal(true)
|
||||
}, [])
|
||||
|
||||
const closeEditModal = useCallback(() => {
|
||||
setShowEditModal(false)
|
||||
}, [])
|
||||
|
||||
const Icon = DOC_FORM_ICON[pipeline.doc_form] || General
|
||||
|
||||
return (
|
||||
<div className='group relative flex h-[132px] cursor-pointer flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg pb-3 shadow-xs shadow-shadow-shadow-3'>
|
||||
<div className='flex items-center gap-x-3 p-4 pb-2'>
|
||||
<div className='relative shrink-0'>
|
||||
<AppIcon
|
||||
size='large'
|
||||
iconType={pipeline.icon_type}
|
||||
icon={pipeline.icon_type === 'image' ? pipeline.file_id : pipeline.icon}
|
||||
background={pipeline.icon_type === 'image' ? undefined : pipeline.icon_background}
|
||||
imageUrl={pipeline.icon_type === 'image' ? pipeline.url : undefined}
|
||||
/>
|
||||
<div className='absolute -bottom-1 -right-1 z-10'>
|
||||
<Icon className='size-4' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex grow flex-col gap-y-1 py-px'>
|
||||
<div className='system-md-semibold truncate text-text-secondary' title={pipeline.name}>{pipeline.name}</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>
|
||||
{t(`dataset.chunkingMode.${DOC_FORM_TEXT[pipeline.doc_form]}`)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className='system-xs-regular line-clamp-3 grow px-4 py-1 text-text-tertiary' title={pipeline.description}>
|
||||
{pipeline.description}
|
||||
</p>
|
||||
<div className='absolute bottom-0 left-0 z-10 hidden w-full items-center gap-x-1 bg-pipeline-template-card-hover-bg p-4 pt-8 group-hover:flex'>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
console.log('Choose', pipeline)
|
||||
}}
|
||||
className='grow gap-x-0.5'
|
||||
>
|
||||
<RiAddLine className='size-4' />
|
||||
<span className='px-0.5'>Choose</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant='secondary'
|
||||
onClick={() => {
|
||||
console.log('details', pipeline)
|
||||
}}
|
||||
className='grow gap-x-0.5'
|
||||
>
|
||||
<RiArrowRightUpLine className='size-4' />
|
||||
<span className='px-0.5'>Details</span>
|
||||
</Button>
|
||||
{
|
||||
showMoreOperations && (
|
||||
<CustomPopover
|
||||
htmlContent={
|
||||
<Operations
|
||||
openEditModal={openEditModal}
|
||||
onDelete={() => {
|
||||
console.log('Delete', pipeline)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
className={'z-20 min-w-[160px]'}
|
||||
popupClassName={'rounded-xl bg-none shadow-none ring-0 min-w-[160px]'}
|
||||
position='br'
|
||||
trigger='click'
|
||||
btnElement={
|
||||
<RiMoreFill className='size-4 text-text-tertiary' />
|
||||
}
|
||||
btnClassName='size-8 cursor-pointer justify-center rounded-lg p-0 shadow-xs shadow-shadow-shadow-3'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<Modal
|
||||
isShow={showEditModal}
|
||||
onClose={closeEditModal}
|
||||
className='max-w-[520px] p-0'
|
||||
>
|
||||
<EditPipelineInfo
|
||||
pipeline={pipeline}
|
||||
onClose={closeEditModal}
|
||||
onSave={() => {
|
||||
console.log('Save', pipeline)
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(TemplateCard)
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import Divider from '@/app/components/base/divider'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type OperationsProps = {
|
||||
openEditModal: () => void
|
||||
onDelete: () => void
|
||||
}
|
||||
|
||||
const Operations = ({
|
||||
openEditModal,
|
||||
onDelete,
|
||||
}: OperationsProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onClickEdit = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
openEditModal()
|
||||
}
|
||||
|
||||
const onClickDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
onDelete()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative flex w-full flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5'>
|
||||
<div className='flex flex-col p-1'>
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={onClickEdit}
|
||||
>
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
Edit Info
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={() => { console.log('Export DSL') }}
|
||||
>
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
Export DSL
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Divider type='horizontal' className='my-0 bg-divider-subtle' />
|
||||
<div className='flex flex-col p-1'>
|
||||
<div
|
||||
className='group flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-destructive-hover'
|
||||
onClick={onClickDelete}
|
||||
>
|
||||
<span className='system-md-regular px-1 text-text-secondary group-hover:text-text-destructive'>
|
||||
{t('common.operation.delete')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Operations)
|
||||
|
|
@ -24,14 +24,14 @@ import Operations from './operations'
|
|||
|
||||
const EXTERNAL_PROVIDER = 'external'
|
||||
|
||||
const DOC_FORM_ICON: Record<ChunkingMode, React.ComponentType<{ className: string }>> = {
|
||||
export const DOC_FORM_ICON: Record<ChunkingMode, React.ComponentType<{ className: string }>> = {
|
||||
[ChunkingMode.text]: General,
|
||||
[ChunkingMode.qa]: Qa,
|
||||
[ChunkingMode.parentChild]: ParentChild,
|
||||
[ChunkingMode.graph]: Graph,
|
||||
}
|
||||
|
||||
const DOC_FORM_TEXT: Record<ChunkingMode, string> = {
|
||||
export const DOC_FORM_TEXT: Record<ChunkingMode, string> = {
|
||||
[ChunkingMode.text]: 'general',
|
||||
[ChunkingMode.qa]: 'qa',
|
||||
[ChunkingMode.parentChild]: 'parentChild',
|
||||
|
|
@ -216,7 +216,7 @@ const DatasetCard = ({
|
|||
}
|
||||
btnClassName={open =>
|
||||
cn(
|
||||
'size-9 cursor-pointer justify-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0 p-0.5 shadow-lg shadow-shadow-shadow-5 ring-[2px] ring-inset ring-components-actionbar-bg hover:border-components-actionbar-border hover:bg-state-base-hover',
|
||||
'size-9 cursor-pointer justify-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0 shadow-lg shadow-shadow-shadow-5 ring-[2px] ring-inset ring-components-actionbar-bg hover:border-components-actionbar-border hover:bg-state-base-hover',
|
||||
open ? 'border-components-actionbar-border bg-state-base-hover' : '',
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ const config = {
|
|||
'node-data-source-bg': 'var(--color-node-data-source-bg)',
|
||||
'tag-selector-mask-bg': 'var(--color-tag-selector-mask-bg)',
|
||||
'tag-selector-mask-hover-bg': 'var(--color-tag-selector-mask-hover-bg)',
|
||||
'pipeline-template-card-hover-bg': 'var(--color-pipeline-template-card-hover-bg)',
|
||||
},
|
||||
animation: {
|
||||
'spin-slow': 'spin 2s linear infinite',
|
||||
|
|
|
|||
|
|
@ -66,4 +66,5 @@ html[data-theme="dark"] {
|
|||
--color-node-data-source-bg: linear-gradient(100deg, var(--workflow-block-wrapper-bg-1, #E9EBF0) 0%, var(--workflow-block-wrapper-bg-2, rgba(233, 235, 240, 0.20)) 100%);
|
||||
--color-tag-selector-mask-bg: linear-gradient(90deg, rgba(34, 34, 37, 0) 0%, rgba(34, 34, 37, 1) 100%);
|
||||
--color-tag-selector-mask-hover-bg: linear-gradient(90deg, rgba(39, 39, 43, 0) 0%, rgba(39, 39, 43, 1) 100%);
|
||||
--color-pipeline-template-card-hover-bg: linear-gradient(0deg, rgba(58, 58, 64, 1) 60.27%, rgba(58, 58, 64, 0) 100%);
|
||||
}
|
||||
|
|
@ -66,4 +66,5 @@ html[data-theme="light"] {
|
|||
--color-node-data-source-bg: linear-gradient(100deg, var(--workflow-block-wrapper-bg-1, #E9EBF0) 0%, var(--workflow-block-wrapper-bg-2, rgba(233, 235, 240, 0.20)) 100%);
|
||||
--color-tag-selector-mask-bg: linear-gradient(90deg, rgba(252, 252, 253, 0) 0%, rgba(252, 252, 253, 1) 100%);
|
||||
--color-tag-selector-mask-hover-bg: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
|
||||
--color-pipeline-template-card-hover-bg: linear-gradient(0deg, rgba(249, 250, 251, 1) 60.27%, rgba(249, 250, 251, 0) 100%);
|
||||
}
|
||||
Loading…
Reference in New Issue