mirror of
https://github.com/langgenius/dify.git
synced 2026-01-08 07:14:14 +00:00
feat(pipeline): implement footer component for dataset creation and enhance UI with new styles
This commit is contained in:
@@ -9,7 +9,7 @@ import { useMembers } from '@/service/use-common'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import PermissionSelector from '../../settings/permission-selector'
|
||||
import PermissionSelector from '@/app/components/datasets/settings/permission-selector'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
@@ -68,11 +68,6 @@ const CreateFromDSLModal = ({
|
||||
setFileContent('')
|
||||
}
|
||||
|
||||
// todo: TBD billing plan
|
||||
// const plan = useProviderContextSelector(state => state.plan)
|
||||
// const enableBilling = useProviderContextSelector(state => state.enableBilling)
|
||||
// const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||
|
||||
const isCreatingRef = useRef(false)
|
||||
|
||||
const { mutateAsync: importDSL } = useImportPipelineDSL()
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { RemixiconComponentType } from '@remixicon/react'
|
||||
import React from 'react'
|
||||
|
||||
type ItemProps = {
|
||||
Icon: RemixiconComponentType
|
||||
title: string
|
||||
description: string
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const Item = ({
|
||||
Icon,
|
||||
title,
|
||||
description,
|
||||
onClick,
|
||||
}: ItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className='group flex w-[337px] cursor-pointer items-center gap-x-3 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 shadow-xs shadow-shadow-shadow-3 hover:shadow-md hover:shadow-shadow-shadow-5'
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className='flex size-10 shrink-0 items-center justify-center rounded-[10px] border border-dashed border-divider-regular bg-background-section group-hover:border-state-accent-hover-alt group-hover:bg-state-accent-hover'>
|
||||
<Icon className='size-5 text-text-quaternary group-hover:text-text-accent' />
|
||||
</div>
|
||||
<div className='flex grow flex-col gap-y-0.5 py-px'>
|
||||
<div className='system-md-semibold truncate text-text-secondary'>
|
||||
{title}
|
||||
</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Item)
|
||||
@@ -1,20 +1,16 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import Item from './item'
|
||||
import { RiAddCircleFill, RiFileUploadLine } from '@remixicon/react'
|
||||
import CreateFromScratchModal from './create-from-scratch-modal'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import CreateFromDSLModal, { CreateFromDSLModalTab } from './create-from-dsl-modal'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { RiFileUploadLine } from '@remixicon/react'
|
||||
import Divider from '../../base/divider'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CreateFromDSLModal, { CreateFromDSLModalTab } from './create-options/create-from-dsl-modal'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useResetDatasetList } from '@/service/knowledge/use-dataset'
|
||||
|
||||
const CreateOptions = () => {
|
||||
const Footer = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||
const [showImportModal, setShowImportModal] = useState(false)
|
||||
|
||||
const onPlanInfoChanged = useProviderContextSelector(state => state.onPlanInfoChanged)
|
||||
const searchParams = useSearchParams()
|
||||
const { replace } = useRouter()
|
||||
const dslUrl = searchParams.get('remoteInstallUrl') || undefined
|
||||
@@ -27,14 +23,6 @@ const CreateOptions = () => {
|
||||
return undefined
|
||||
}, [dslUrl])
|
||||
|
||||
const openCreateFromScratch = useCallback(() => {
|
||||
setShowCreateModal(true)
|
||||
}, [])
|
||||
|
||||
const closeCreateFromScratch = useCallback(() => {
|
||||
setShowCreateModal(false)
|
||||
}, [])
|
||||
|
||||
const openImportFromDSL = useCallback(() => {
|
||||
setShowImportModal(true)
|
||||
}, [])
|
||||
@@ -46,28 +34,20 @@ const CreateOptions = () => {
|
||||
}, [dslUrl, replace])
|
||||
|
||||
const onImportFromDSLSuccess = useCallback(() => {
|
||||
onPlanInfoChanged()
|
||||
resetDatasetList()
|
||||
}, [onPlanInfoChanged, resetDatasetList])
|
||||
}, [resetDatasetList])
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-3 px-16 py-2'>
|
||||
<Item
|
||||
Icon={RiAddCircleFill}
|
||||
title={t('datasetPipeline.creation.createFromScratch.title')}
|
||||
description={t('datasetPipeline.creation.createFromScratch.description')}
|
||||
onClick={openCreateFromScratch}
|
||||
/>
|
||||
<Item
|
||||
Icon={RiFileUploadLine}
|
||||
title={t('datasetPipeline.creation.ImportDSL.title')}
|
||||
description={t('datasetPipeline.creation.ImportDSL.description')}
|
||||
<div className='absolute bottom-0 left-0 right-0 z-10 flex flex-col gap-y-4 bg-knowledge-pipeline-creation-footer-bg px-16 pb-6 backdrop-blur-[6px]'>
|
||||
<Divider type='horizontal' className='my-0 w-8' />
|
||||
<button
|
||||
type='button'
|
||||
className='system-md-medium flex items-center gap-x-3 text-text-accent'
|
||||
onClick={openImportFromDSL}
|
||||
/>
|
||||
<CreateFromScratchModal
|
||||
show={showCreateModal}
|
||||
onClose={closeCreateFromScratch}
|
||||
/>
|
||||
>
|
||||
<RiFileUploadLine className='size-5' />
|
||||
<span>{t('datasetPipeline.creation.importDSL')}</span>
|
||||
</button>
|
||||
<CreateFromDSLModal
|
||||
show={showImportModal}
|
||||
onClose={onCloseImportModal}
|
||||
@@ -79,4 +59,4 @@ const CreateOptions = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateOptions
|
||||
export default React.memo(Footer)
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
import Header from './header'
|
||||
import CreateOptions from './create-options'
|
||||
import List from './list'
|
||||
import Effect from '../../base/effect'
|
||||
import Footer from './footer'
|
||||
|
||||
const CreateFromPipeline = () => {
|
||||
return (
|
||||
@@ -11,8 +11,8 @@ const CreateFromPipeline = () => {
|
||||
>
|
||||
<Effect className='left-8 top-[-34px] opacity-20' />
|
||||
<Header />
|
||||
<CreateOptions />
|
||||
<List />
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { usePipelineTemplateList } from '@/service/use-pipeline'
|
||||
import TemplateCard from './template-card'
|
||||
import CreateCard from './create-card'
|
||||
|
||||
const BuiltInPipelineList = () => {
|
||||
const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'built-in' })
|
||||
const list = pipelineList?.pipeline_templates
|
||||
|
||||
if (isLoading || !list)
|
||||
return null
|
||||
const list = pipelineList?.pipeline_templates || []
|
||||
|
||||
return (
|
||||
<div className='grid grid-cols-1 gap-3 py-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
||||
{list.map((pipeline, index) => (
|
||||
<div className='grid grid-cols-1 gap-3 py-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
||||
<CreateCard />
|
||||
{!isLoading && list.map((pipeline, index) => (
|
||||
<TemplateCard
|
||||
key={index}
|
||||
type='built-in'
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddCircleLine } from '@remixicon/react'
|
||||
import CreateFromScratchModal from '../create-options/create-from-scratch-modal'
|
||||
|
||||
const CreateCard = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||
|
||||
const openCreateFromScratch = useCallback(() => {
|
||||
setShowCreateModal(true)
|
||||
}, [])
|
||||
|
||||
const closeCreateFromScratch = useCallback(() => {
|
||||
setShowCreateModal(false)
|
||||
}, [])
|
||||
|
||||
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'
|
||||
onClick={openCreateFromScratch}
|
||||
>
|
||||
<div className='flex items-center gap-x-3 p-4 pb-2'>
|
||||
<div className='flex size-10 shrink-0 items-center justify-center rounded-[10px] border border-dashed border-divider-regular bg-background-section group-hover:border-state-accent-hover-alt group-hover:bg-state-accent-hover'>
|
||||
<RiAddCircleLine className='size-5 text-text-quaternary group-hover:text-text-accent' />
|
||||
</div>
|
||||
<div className='system-md-semibold truncate text-text-primary'>
|
||||
{t('datasetPipeline.creation.createFromScratch.title')}
|
||||
</div>
|
||||
</div>
|
||||
<p className='system-xs-regular line-clamp-3 px-4 py-1 text-text-tertiary'>
|
||||
{t('datasetPipeline.creation.createFromScratch.description')}
|
||||
</p>
|
||||
<CreateFromScratchModal
|
||||
show={showCreateModal}
|
||||
onClose={closeCreateFromScratch}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(CreateCard)
|
||||
@@ -1,23 +1,28 @@
|
||||
import TemplateCard from './template-card'
|
||||
import { usePipelineTemplateList } from '@/service/use-pipeline'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const CustomizedList = () => {
|
||||
const { t } = useTranslation()
|
||||
const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'customized' })
|
||||
const list = pipelineList?.pipeline_templates
|
||||
const list = pipelineList?.pipeline_templates || []
|
||||
|
||||
if (isLoading || !list)
|
||||
if (isLoading || list.length === 0)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='grid grid-cols-1 gap-3 py-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
||||
{list.map((pipeline, index) => (
|
||||
<TemplateCard
|
||||
key={index}
|
||||
type='customized'
|
||||
pipeline={pipeline}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<div className='system-sm-semibold-uppercase pt-2 text-text-tertiary'>{t('datasetPipeline.templates.customized')}</div>
|
||||
<div className='grid grid-cols-1 gap-3 py-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
||||
{list.map((pipeline, index) => (
|
||||
<TemplateCard
|
||||
key={index}
|
||||
type='customized'
|
||||
pipeline={pipeline}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,11 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import Tab from './tab'
|
||||
import BuiltInPipelineList from './built-in-pipeline-list'
|
||||
import CustomizedList from './customized-list'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const List = () => {
|
||||
const { t } = useTranslation()
|
||||
const [activeTab, setActiveTab] = useState('built-in')
|
||||
|
||||
const options = useMemo(() => {
|
||||
return [
|
||||
{ value: 'built-in', label: t('datasetPipeline.tabs.builtInPipeline') },
|
||||
{ value: 'customized', label: t('datasetPipeline.tabs.customized') },
|
||||
]
|
||||
}, [t])
|
||||
|
||||
const handleTabChange = useCallback((tab: string) => {
|
||||
setActiveTab(tab)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='flex grow flex-col overflow-hidden'>
|
||||
<Tab
|
||||
activeTab={activeTab}
|
||||
handleTabChange={handleTabChange}
|
||||
options={options}
|
||||
/>
|
||||
<div className='grow overflow-y-auto px-16'>
|
||||
{
|
||||
activeTab === 'built-in' && <BuiltInPipelineList />
|
||||
}
|
||||
{
|
||||
activeTab === 'customized' && <CustomizedList />
|
||||
}
|
||||
</div>
|
||||
<div className='grow gap-y-1 overflow-y-auto px-16 pb-[60px] pt-1'>
|
||||
<BuiltInPipelineList />
|
||||
<CustomizedList />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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)
|
||||
@@ -1,29 +0,0 @@
|
||||
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)
|
||||
@@ -1,21 +1,17 @@
|
||||
const translation = {
|
||||
creation: {
|
||||
title: 'Create knowledge pipeline',
|
||||
title: 'Blank Knowledge Pipeline',
|
||||
createFromScratch: {
|
||||
title: 'Create from scratch',
|
||||
description: 'Blank knowledge pipeline',
|
||||
},
|
||||
ImportDSL: {
|
||||
title: 'Import',
|
||||
description: 'Import from a DSL file',
|
||||
title: 'Blank knowledge pipeline',
|
||||
description: 'Create a custom pipeline from scratch with full control over data processing and structure.',
|
||||
},
|
||||
importDSL: 'Import from a DSL file',
|
||||
createKnowledge: 'Create Knowledge',
|
||||
errorTip: 'Failed to create a Knowledge Base',
|
||||
successTip: 'Successfully created a Knowledge Base',
|
||||
caution: 'Caution',
|
||||
},
|
||||
tabs: {
|
||||
builtInPipeline: 'Built-in pipeline',
|
||||
templates: {
|
||||
customized: 'Customized',
|
||||
},
|
||||
operations: {
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
const translation = {
|
||||
creation: {
|
||||
title: '创建知识库 pipeline',
|
||||
title: '创建知识流水线',
|
||||
createFromScratch: {
|
||||
title: '从零开始创建',
|
||||
description: '空白知识库 pipeline',
|
||||
title: '空白知识流水线',
|
||||
description: '从零开始创建一个自定义知识流水线,对数据处理和结构拥有完全控制权。',
|
||||
},
|
||||
ImportDSL: {
|
||||
title: '导入',
|
||||
description: '从 DSL 文件导入',
|
||||
},
|
||||
createKnowledge: '创建知识库',
|
||||
errorTip: '创建知识库',
|
||||
successTip: '成功创建知识库',
|
||||
importDSL: '从 DSL 文件导入',
|
||||
createKnowledge: '创建知识流水线',
|
||||
errorTip: '创建知识流水线失败',
|
||||
successTip: '成功创建知识流水线',
|
||||
caution: '注意',
|
||||
},
|
||||
tabs: {
|
||||
builtInPipeline: '内置 pipeline',
|
||||
templates: {
|
||||
customized: '自定义',
|
||||
},
|
||||
operations: {
|
||||
@@ -23,13 +19,13 @@ const translation = {
|
||||
details: '详情',
|
||||
editInfo: '编辑信息',
|
||||
exportDSL: '导出 DSL',
|
||||
useTemplate: '使用此知识库 pipeline',
|
||||
useTemplate: '使用此知识流水线',
|
||||
backToDataSource: '返回数据源',
|
||||
process: '处理',
|
||||
dataSource: '数据源',
|
||||
saveAndProcess: '保存并处理',
|
||||
preview: '预览',
|
||||
exportPipeline: '导出 pipeline',
|
||||
exportPipeline: '导出知识流水线',
|
||||
convert: '转换',
|
||||
},
|
||||
knowledgeNameAndIcon: '知识库名称和图标',
|
||||
@@ -37,15 +33,15 @@ const translation = {
|
||||
knowledgeDescription: '知识库描述',
|
||||
knowledgeDescriptionPlaceholder: '描述知识库中的内容。详细的描述可以让 AI 更准确地访问数据集的内容。如果为空,Dify 将使用默认的命中策略。(可选)',
|
||||
knowledgePermissions: '权限',
|
||||
editPipelineInfo: '编辑 pipeline 信息',
|
||||
pipelineNameAndIcon: 'pipeline 名称和图标',
|
||||
editPipelineInfo: '编辑知识流水线信息',
|
||||
pipelineNameAndIcon: '知识流水线名称和图标',
|
||||
deletePipeline: {
|
||||
title: '要删除此 pipeline 模板吗?',
|
||||
content: '删除 pipeline 模板是不可逆的。',
|
||||
title: '要删除此知识流水线模板吗?',
|
||||
content: '删除知识流水线模板是不可逆的。',
|
||||
},
|
||||
exportDSL: {
|
||||
successTip: '成功导出 pipeline DSL',
|
||||
errorTip: '导出 pipeline DSL 失败',
|
||||
successTip: '成功导出知识流水线 DSL',
|
||||
errorTip: '导出知识流水线 DSL 失败',
|
||||
},
|
||||
details: {
|
||||
createdBy: '由 {{author}} 创建',
|
||||
@@ -70,7 +66,7 @@ const translation = {
|
||||
inputField: '输入字段',
|
||||
inputFieldPanel: {
|
||||
title: '用户输入字段',
|
||||
description: '用户输入字段用于定义和收集 pipeline 执行过程中所需的变量,用户可以自定义字段类型,并灵活配置输入,以满足不同数据源或文档处理的需求。',
|
||||
description: '用户输入字段用于定义和收集知识流水线执行过程中所需的变量,用户可以自定义字段类型,并灵活配置输入,以满足不同数据源或文档处理的需求。',
|
||||
uniqueInputs: {
|
||||
title: '非共享输入',
|
||||
tooltip: '非共享输入只能被选定的数据源及其下游节点访问。用户在选择其他数据源时不需要填写它。只有数据源变量引用的输入字段才会出现在第一步(数据源)中。所有其他字段将在第二步(Process Documents)中显示。',
|
||||
@@ -136,16 +132,16 @@ const translation = {
|
||||
},
|
||||
configurationTip: '配置 {{pluginName}}',
|
||||
conversion: {
|
||||
title: '转换为知识库 pipeline',
|
||||
descriptionChunk1: '您现在可以将现有知识库转换为使用知识库 pipeline 来处理文档',
|
||||
title: '转换为知识流水线',
|
||||
descriptionChunk1: '您现在可以将现有知识库转换为使用知识流水线来处理文档',
|
||||
descriptionChunk2: ' —— 这是一种更开放、更灵活的方式,可以访问我们市场中的插件。新的处理方式将应用到后续添加的所有文档。',
|
||||
warning: '此操作无法撤销。',
|
||||
confirm: {
|
||||
title: '确认',
|
||||
content: '此操作是永久性的。您将无法恢复到之前的方式。请确认转换。',
|
||||
},
|
||||
errorMessage: '转换数据集为 pipeline 失败',
|
||||
successMessage: '成功将数据集转换为 pipeline',
|
||||
errorMessage: '转换数据集为知识流水线失败',
|
||||
successMessage: '成功将数据集转换为知识流水线',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ const config = {
|
||||
'billing-plan-title-bg': 'var(--color-billing-plan-title-bg)',
|
||||
'billing-plan-card-premium-bg': 'var(--color-billing-plan-card-premium-bg)',
|
||||
'billing-plan-card-enterprise-bg': 'var(--color-billing-plan-card-enterprise-bg)',
|
||||
'knowledge-pipeline-creation-footer-bg': 'var(--color-knowledge-pipeline-creation-footer-bg)',
|
||||
},
|
||||
animation: {
|
||||
'spin-slow': 'spin 2s linear infinite',
|
||||
|
||||
@@ -72,4 +72,5 @@ html[data-theme="dark"] {
|
||||
--color-billing-plan-title-bg: linear-gradient(95deg, #0A68FF 29.47%, #03F 105.31%);
|
||||
--color-billing-plan-card-premium-bg: linear-gradient(180deg, #F90 0%, rgba(255, 153, 0, 0.00) 100%);
|
||||
--color-billing-plan-card-enterprise-bg: linear-gradient(180deg, #03F 0%, rgba(0, 51, 255, 0.00) 100%);
|
||||
--color-knowledge-pipeline-creation-footer-bg: linear-gradient(90deg, rgba(34, 34, 37, 1) 4.89%, rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
|
||||
@@ -72,4 +72,5 @@ html[data-theme="light"] {
|
||||
--color-billing-plan-title-bg: linear-gradient(95deg, #03F 29.47%, #03F 105.31%);
|
||||
--color-billing-plan-card-premium-bg: linear-gradient(180deg, #F90 0%, rgba(255, 153, 0, 0.00) 100%);
|
||||
--color-billing-plan-card-enterprise-bg: linear-gradient(180deg, #03F 0%, rgba(0, 51, 255, 0.00) 100%);
|
||||
--color-knowledge-pipeline-creation-footer-bg: linear-gradient(90deg, #FCFCFD 4.89%, rgba(255, 255, 255, 0.00) 100%);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user