From b18519b824ba387af50991c3b67e295b87ec21fb Mon Sep 17 00:00:00 2001 From: twwu Date: Wed, 21 May 2025 16:37:02 +0800 Subject: [PATCH] refactor: add create-from-pipeline page and associated components for document processing --- .../documents/create-from-pipeline/page.tsx | 10 + .../[datasetId]/layout-main.tsx | 2 +- web/app/(commonLayout)/datasets/Datasets.tsx | 96 -------- .../datasets/NewDatasetCard.tsx | 42 ---- .../datasets/create-from-pipeline/index.tsx | 3 +- .../documents/create-from-pipeline/hooks.ts | 21 ++ .../documents/create-from-pipeline/index.tsx | 217 ++++++++++++++++++ .../create-from-pipeline/left-header.tsx | 46 ++++ .../create-from-pipeline/step-indicator.tsx | 33 +++ .../documents/create-from-pipeline/types.tsx | 5 + .../detail/completed/segment-card/index.tsx | 20 +- .../datasets/documents/detail/index.tsx | 5 +- .../components/datasets/documents/index.tsx | 11 +- web/i18n/en-US/dataset-pipeline.ts | 9 + web/i18n/zh-Hans/dataset-pipeline.ts | 9 + web/models/pipeline.ts | 29 ++- web/service/use-pipeline.ts | 10 + web/tailwind-common-config.ts | 1 + web/themes/manual-dark.css | 1 + web/themes/manual-light.css | 1 + 20 files changed, 414 insertions(+), 157 deletions(-) create mode 100644 web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create-from-pipeline/page.tsx delete mode 100644 web/app/(commonLayout)/datasets/Datasets.tsx delete mode 100644 web/app/(commonLayout)/datasets/NewDatasetCard.tsx create mode 100644 web/app/components/datasets/documents/create-from-pipeline/hooks.ts create mode 100644 web/app/components/datasets/documents/create-from-pipeline/index.tsx create mode 100644 web/app/components/datasets/documents/create-from-pipeline/left-header.tsx create mode 100644 web/app/components/datasets/documents/create-from-pipeline/step-indicator.tsx create mode 100644 web/app/components/datasets/documents/create-from-pipeline/types.tsx diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create-from-pipeline/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create-from-pipeline/page.tsx new file mode 100644 index 0000000000..9ce86bbef4 --- /dev/null +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create-from-pipeline/page.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import CreateFromPipeline from '@/app/components/datasets/documents/create-from-pipeline' + +const CreateFromPipelinePage = async () => { + return ( + + ) +} + +export default CreateFromPipelinePage diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 5798092394..5eb459bf1b 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -109,7 +109,7 @@ const DatasetDetailLayout: FC = (props) => { params: { datasetId }, } = props const pathname = usePathname() - const hideSideBar = /documents\/create$/.test(pathname) + const hideSideBar = pathname.endsWith('documents/create') || pathname.endsWith('documents/create-from-pipeline') const { t } = useTranslation() const { isCurrentWorkspaceDatasetOperator } = useAppContext() diff --git a/web/app/(commonLayout)/datasets/Datasets.tsx b/web/app/(commonLayout)/datasets/Datasets.tsx deleted file mode 100644 index 778c46cd61..0000000000 --- a/web/app/(commonLayout)/datasets/Datasets.tsx +++ /dev/null @@ -1,96 +0,0 @@ -'use client' - -import { useCallback, useEffect, useRef } from 'react' -import useSWRInfinite from 'swr/infinite' -import { debounce } from 'lodash-es' -import NewDatasetCard from './NewDatasetCard' -import DatasetCard from '@/app/components/datasets/list/dataset-card' -import type { DataSetListResponse, FetchDatasetsParams } from '@/models/datasets' -import { fetchDatasets } from '@/service/datasets' -import { useAppContext } from '@/context/app-context' -import { useTranslation } from 'react-i18next' - -const getKey = ( - pageIndex: number, - previousPageData: DataSetListResponse, - tags: string[], - keyword: string, - includeAll: boolean, -) => { - if (!pageIndex || previousPageData.has_more) { - const params: FetchDatasetsParams = { - url: 'datasets', - params: { - page: pageIndex + 1, - limit: 30, - include_all: includeAll, - }, - } - if (tags.length) - params.params.tag_ids = tags - if (keyword) - params.params.keyword = keyword - return params - } - return null -} - -type Props = { - containerRef: React.RefObject - tags: string[] - keywords: string - includeAll: boolean -} - -const Datasets = ({ - containerRef, - tags, - keywords, - includeAll, -}: Props) => { - const { t } = useTranslation() - const { isCurrentWorkspaceEditor } = useAppContext() - const { data, isLoading, setSize, mutate } = useSWRInfinite( - (pageIndex: number, previousPageData: DataSetListResponse) => getKey(pageIndex, previousPageData, tags, keywords, includeAll), - fetchDatasets, - { revalidateFirstPage: false, revalidateAll: true }, - ) - const loadingStateRef = useRef(false) - const anchorRef = useRef(null) - - useEffect(() => { - loadingStateRef.current = isLoading - }, [isLoading, t]) - - const onScroll = useCallback( - debounce(() => { - if (!loadingStateRef.current && containerRef.current && anchorRef.current) { - const { scrollTop, clientHeight } = containerRef.current - const anchorOffset = anchorRef.current.offsetTop - if (anchorOffset - scrollTop - clientHeight < 100) - setSize(size => size + 1) - } - }, 50), - [setSize], - ) - - useEffect(() => { - const currentContainer = containerRef.current - currentContainer?.addEventListener('scroll', onScroll) - return () => { - currentContainer?.removeEventListener('scroll', onScroll) - onScroll.cancel() - } - }, [onScroll]) - - return ( - - ) -} - -export default Datasets diff --git a/web/app/(commonLayout)/datasets/NewDatasetCard.tsx b/web/app/(commonLayout)/datasets/NewDatasetCard.tsx deleted file mode 100644 index 792d9904da..0000000000 --- a/web/app/(commonLayout)/datasets/NewDatasetCard.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client' -import { useTranslation } from 'react-i18next' -import { basePath } from '@/utils/var' -import { - RiAddLine, - RiArrowRightLine, -} from '@remixicon/react' - -const CreateAppCard = ( - { - ref, - ..._ - }, -) => { - const { t } = useTranslation() - - return ( - - ) -} - -CreateAppCard.displayName = 'CreateAppCard' - -export default CreateAppCard diff --git a/web/app/components/datasets/create-from-pipeline/index.tsx b/web/app/components/datasets/create-from-pipeline/index.tsx index 194d5a5204..51f9b472fb 100644 --- a/web/app/components/datasets/create-from-pipeline/index.tsx +++ b/web/app/components/datasets/create-from-pipeline/index.tsx @@ -7,8 +7,7 @@ import Effect from '../../base/effect' const CreateFromPipeline = () => { return (
diff --git a/web/app/components/datasets/documents/create-from-pipeline/hooks.ts b/web/app/components/datasets/documents/create-from-pipeline/hooks.ts new file mode 100644 index 0000000000..1837466f79 --- /dev/null +++ b/web/app/components/datasets/documents/create-from-pipeline/hooks.ts @@ -0,0 +1,21 @@ +import { useTranslation } from 'react-i18next' +import { AddDocumentsStep } from './types' + +export const useAddDocumentsSteps = () => { + const { t } = useTranslation() + const steps = [ + { + label: t('datasetPipeline.addDocuments.steps.chooseDatasource'), + value: AddDocumentsStep.dataSource, + }, + { + label: t('datasetPipeline.addDocuments.steps.ProcessDocuments'), + value: AddDocumentsStep.processDocuments, + }, + { + label: t('datasetPipeline.addDocuments.steps.ProcessingDocuments'), + value: AddDocumentsStep.processingDocuments, + }, + ] + return steps +} diff --git a/web/app/components/datasets/documents/create-from-pipeline/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/index.tsx new file mode 100644 index 0000000000..7736b07eda --- /dev/null +++ b/web/app/components/datasets/documents/create-from-pipeline/index.tsx @@ -0,0 +1,217 @@ +'use client' +import { useCallback, useMemo, useState } from 'react' +// import StepIndicator from './step-indicator' +// import { useTestRunSteps } from './hooks' +// import DataSourceOptions from './data-source-options' +import type { CrawlResultItem, FileItem } from '@/models/datasets' +import { DataSourceType } from '@/models/datasets' +// import LocalFile from './data-source/local-file' +import produce from 'immer' +import { useProviderContextSelector } from '@/context/provider-context' +import { DataSourceProvider, type NotionPage } from '@/models/common' +// import Notion from './data-source/notion' +import VectorSpaceFull from '@/app/components/billing/vector-space-full' +// import Firecrawl from './data-source/website/firecrawl' +// import JinaReader from './data-source/website/jina-reader' +// import WaterCrawl from './data-source/website/water-crawl' +// import Actions from './data-source/actions' +// import DocumentProcessing from './document-processing' +import { useTranslation } from 'react-i18next' +import type { Datasource } from '@/app/components/rag-pipeline/components/panel/test-run/types' +import LocalFile from '@/app/components/rag-pipeline/components/panel/test-run/data-source/local-file' +import Notion from '@/app/components/rag-pipeline/components/panel/test-run/data-source/notion' +import FireCrawl from '@/app/components/rag-pipeline/components/panel/test-run/data-source/website/firecrawl' +import JinaReader from '@/app/components/rag-pipeline/components/panel/test-run/data-source/website/jina-reader' +import WaterCrawl from '@/app/components/rag-pipeline/components/panel/test-run/data-source/website/water-crawl' +import Actions from '@/app/components/rag-pipeline/components/panel/test-run/data-source/actions' +import DocumentProcessing from '@/app/components/rag-pipeline/components/panel/test-run/document-processing' +import LeftHeader from './left-header' +// import { usePipelineRun } from '../../../hooks' +// import type { Datasource } from './types' + +const TestRunPanel = () => { + const { t } = useTranslation() + const [currentStep, setCurrentStep] = useState(1) + const [datasource, setDatasource] = useState() + const [fileList, setFiles] = useState([]) + const [notionPages, setNotionPages] = useState([]) + const [websitePages, setWebsitePages] = useState([]) + const [websiteCrawlJobId, setWebsiteCrawlJobId] = useState('') + + const plan = useProviderContextSelector(state => state.plan) + const enableBilling = useProviderContextSelector(state => state.enableBilling) + + // const steps = useTestRunSteps() + + const allFileLoaded = (fileList.length > 0 && fileList.every(file => file.file.id)) + const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace + const isShowVectorSpaceFull = allFileLoaded && isVectorSpaceFull && enableBilling + const notSupportBatchUpload = enableBilling && plan.type === 'sandbox' + const nextDisabled = useMemo(() => { + if (!fileList.length) + return true + if (fileList.some(file => !file.file.id)) + return true + return isShowVectorSpaceFull + }, [fileList, isShowVectorSpaceFull]) + + const nextBtnDisabled = useMemo(() => { + if (!datasource) return true + if (datasource.type === DataSourceType.FILE) + return nextDisabled + if (datasource.type === DataSourceType.NOTION) + return isShowVectorSpaceFull || !notionPages.length + if (datasource.type === DataSourceProvider.fireCrawl + || datasource.type === DataSourceProvider.jinaReader + || datasource.type === DataSourceProvider.waterCrawl) + return isShowVectorSpaceFull || !websitePages.length + return false + }, [datasource, nextDisabled, isShowVectorSpaceFull, notionPages.length, websitePages.length]) + + const updateFile = (fileItem: FileItem, progress: number, list: FileItem[]) => { + const newList = produce(list, (draft) => { + const targetIndex = draft.findIndex(file => file.fileID === fileItem.fileID) + draft[targetIndex] = { + ...draft[targetIndex], + progress, + } + }) + setFiles(newList) + } + + const updateFileList = (preparedFiles: FileItem[]) => { + setFiles(preparedFiles) + } + + const updateNotionPages = (value: NotionPage[]) => { + setNotionPages(value) + } + + const handleNextStep = useCallback(() => { + setCurrentStep(preStep => preStep + 1) + }, []) + + const handleBackStep = useCallback(() => { + setCurrentStep(preStep => preStep - 1) + }, []) + + // const { handleRun } = usePipelineRun() + + const handleProcess = useCallback((data: Record) => { + if (!datasource) + return + const datasourceInfo: Record = {} + let datasource_type = '' + if (datasource.type === DataSourceType.FILE) { + datasource_type = 'local_file' + datasourceInfo.fileId = fileList.map(file => file.fileID) + } + if (datasource.type === DataSourceType.NOTION) { + datasource_type = 'online_document' + datasourceInfo.workspaceId = notionPages[0].workspace_id + datasourceInfo.page = notionPages.map((page) => { + const { workspace_id, ...rest } = page + return rest + }) + } + if (datasource.type === DataSourceProvider.fireCrawl + || datasource.type === DataSourceProvider.jinaReader + || datasource.type === DataSourceProvider.waterCrawl) { + datasource_type = 'website_crawl' + datasourceInfo.jobId = websiteCrawlJobId + datasourceInfo.result = websitePages + } + // handleRun({ + // inputs: data, + // datasource_type, + // datasource_info: datasourceInfo, + // }) + }, [datasource, fileList, notionPages, websiteCrawlJobId, websitePages]) + + return ( +
+
+ +
+ { + currentStep === 1 && ( + <> +
+ {/* */} + {datasource?.type === DataSourceType.FILE && ( + + )} + {datasource?.type === DataSourceType.NOTION && ( + + )} + {datasource?.type === DataSourceProvider.fireCrawl && ( + + )} + {datasource?.type === DataSourceProvider.jinaReader && ( + + )} + {datasource?.type === DataSourceProvider.waterCrawl && ( + + )} + {isShowVectorSpaceFull && ( + + )} +
+ + + ) + } + { + currentStep === 2 && ( + + ) + } +
+
+ {/* Preview */} +
+
+
+ ) +} + +export default TestRunPanel diff --git a/web/app/components/datasets/documents/create-from-pipeline/left-header.tsx b/web/app/components/datasets/documents/create-from-pipeline/left-header.tsx new file mode 100644 index 0000000000..77bb00fc5a --- /dev/null +++ b/web/app/components/datasets/documents/create-from-pipeline/left-header.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import { RiArrowLeftLine } from '@remixicon/react' +import Button from '@/app/components/base/button' +import { useParams } from 'next/navigation' +import Effect from '@/app/components/base/effect' +import { useAddDocumentsSteps } from './hooks' +import StepIndicator from './step-indicator' + +type LeftHeaderProps = { + title: string + currentStep: number +} + +const LeftHeader = ({ + title, + currentStep, +}: LeftHeaderProps) => { + const { datasetId } = useParams() + const steps = useAddDocumentsSteps() + + return ( +
+
+ + {title} + + / + +
+
+ {steps[currentStep - 1]?.label} +
+ + + + +
+ ) +} + +export default React.memo(LeftHeader) diff --git a/web/app/components/datasets/documents/create-from-pipeline/step-indicator.tsx b/web/app/components/datasets/documents/create-from-pipeline/step-indicator.tsx new file mode 100644 index 0000000000..9db7a0446d --- /dev/null +++ b/web/app/components/datasets/documents/create-from-pipeline/step-indicator.tsx @@ -0,0 +1,33 @@ +import cn from '@/utils/classnames' +import React from 'react' + +type Step = { + label: string + value: string +} + +type StepIndicatorProps = { + currentStep: number + steps: Step[] +} + +const StepIndicator = ({ + currentStep, + steps, +}: StepIndicatorProps) => { + return ( +
+ {steps.map((step, index) => { + const isActive = index === currentStep - 1 + return ( +
+ ) + })} +
+ ) +} + +export default React.memo(StepIndicator) diff --git a/web/app/components/datasets/documents/create-from-pipeline/types.tsx b/web/app/components/datasets/documents/create-from-pipeline/types.tsx new file mode 100644 index 0000000000..06c7f7ae09 --- /dev/null +++ b/web/app/components/datasets/documents/create-from-pipeline/types.tsx @@ -0,0 +1,5 @@ +export enum AddDocumentsStep { + dataSource = 'dataSource', + processDocuments = 'processDocuments', + processingDocuments = 'processingDocuments', +} diff --git a/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx b/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx index 584a60bd9a..74360bda0c 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx @@ -1,7 +1,7 @@ import React, { type FC, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' -import { StatusItem } from '../../../list' +import StatusItem from '../../../status-item' import { useDocumentContext } from '../../index' import ChildSegmentList from '../child-segment-list' import Tag from '../common/tag' @@ -228,15 +228,15 @@ const SegmentCard: FC = ({ } { isParagraphMode && child_chunks.length > 0 - && + && } {showModal && = ({ datasetId, documentId }) => { datasetId={datasetId} onUpdate={handleOperate} /> - = ({ datasetId }) => { const [currPage, setCurrPage] = React.useState(0) const [limit, setLimit] = useState(DEFAULT_LIMIT) const router = useRouter() - const { dataset } = useDatasetDetailContext() + const dataset = useDatasetDetailContextWithSelector(s => s.dataset) const [notionPageSelectorModalVisible, setNotionPageSelectorModalVisible] = useState(false) const [timerCanRun, setTimerCanRun] = useState(true) const isDataSourceNotion = dataset?.data_source_type === DataSourceType.NOTION @@ -172,6 +172,11 @@ const Documents: FC = ({ datasetId }) => { const total = documentsRes?.total || 0 const routeToDocCreate = () => { + // if dataset is create from pipeline, redirect to create from pipeline page + if (dataset?.pipeline_id) { + router.push(`/datasets/${datasetId}/documents/create-from-pipeline`) + return + } if (isDataSourceNotion) { setNotionPageSelectorModalVisible(true) return @@ -267,7 +272,7 @@ const Documents: FC = ({ datasetId }) => { ? 'https://docs.dify.ai/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' : 'https://docs.dify.ai/en/guides/knowledge-base/integrate-knowledge-within-application' } - > + > {t('datasetDocuments.list.learnMore')} diff --git a/web/i18n/en-US/dataset-pipeline.ts b/web/i18n/en-US/dataset-pipeline.ts index 5665d42d0b..3fc6b336d1 100644 --- a/web/i18n/en-US/dataset-pipeline.ts +++ b/web/i18n/en-US/dataset-pipeline.ts @@ -70,6 +70,15 @@ const translation = { addInputField: 'Add Input Field', editInputField: 'Edit Input Field', }, + addDocuments: { + title: 'Add Documents', + steps: { + chooseDatasource: 'Choose a Data Source', + processDocuments: 'Process Documents', + processingDocuments: 'Processing Documents', + }, + backToDataSource: 'Data Source', + }, } export default translation diff --git a/web/i18n/zh-Hans/dataset-pipeline.ts b/web/i18n/zh-Hans/dataset-pipeline.ts index aae121bcaf..670d97a18f 100644 --- a/web/i18n/zh-Hans/dataset-pipeline.ts +++ b/web/i18n/zh-Hans/dataset-pipeline.ts @@ -70,6 +70,15 @@ const translation = { addInputField: '添加输入字段', editInputField: '编辑输入字段', }, + addDocuments: { + title: '添加文档', + steps: { + chooseDatasource: '选择数据源', + processDocuments: '处理文档', + processingDocuments: '正在处理文档', + }, + backToDataSource: '数据源', + }, } export default translation diff --git a/web/models/pipeline.ts b/web/models/pipeline.ts index fe501a9216..efd6129488 100644 --- a/web/models/pipeline.ts +++ b/web/models/pipeline.ts @@ -1,4 +1,4 @@ -import type { Edge, Node, SupportUploadFileTypes } from '@/app/components/workflow/types' +import type { Edge, EnvironmentVariable, Node, SupportUploadFileTypes } from '@/app/components/workflow/types' import type { DSLImportMode, DSLImportStatus } from './app' import type { ChunkingMode, DatasetPermission, IconInfo } from './datasets' import type { Dependency } from '@/app/components/plugins/types' @@ -143,3 +143,30 @@ export type PipelineDatasourceNodeRunRequest = { } export type PipelineDatasourceNodeRunResponse = Record + +export type PublishedPipelineInfoResponse = { + id: string + graph: { + nodes: Node[] + edges: Edge[] + viewport: Viewport + } + created_at: number + created_by: { + id: string + name: string + email: string + } + hash: string + updated_at: number + updated_by: { + id: string + name: string + email: string + }, + environment_variables?: EnvironmentVariable[] + rag_pipeline_variables?: RAGPipelineVariables + version: string + marked_name: string + marked_comment: string +} diff --git a/web/service/use-pipeline.ts b/web/service/use-pipeline.ts index 9c33e9c278..9b1215ab8a 100644 --- a/web/service/use-pipeline.ts +++ b/web/service/use-pipeline.ts @@ -15,6 +15,7 @@ import type { PipelineTemplateByIdResponse, PipelineTemplateListParams, PipelineTemplateListResponse, + PublishedPipelineInfoResponse, UpdateTemplateInfoRequest, UpdateTemplateInfoResponse, } from '@/models/pipeline' @@ -156,3 +157,12 @@ export const useDataSourceList = (enabled?: boolean) => { retry: false, }) } + +export const usePublishedPipelineInfo = (pipelineId: string) => { + return useQuery({ + queryKey: [NAME_SPACE, 'published-pipeline', pipelineId], + queryFn: () => { + return get(`/rag/pipelines/${pipelineId}/workflows/publish`) + }, + }) +} diff --git a/web/tailwind-common-config.ts b/web/tailwind-common-config.ts index 16279aa1b6..fc03cb321d 100644 --- a/web/tailwind-common-config.ts +++ b/web/tailwind-common-config.ts @@ -124,6 +124,7 @@ const config = { '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)', + 'pipeline-add-documents-title-bg': 'var(--color-pipeline-add-documents-title-bg)', }, animation: { 'spin-slow': 'spin 2s linear infinite', diff --git a/web/themes/manual-dark.css b/web/themes/manual-dark.css index aa8a62511e..4a837a6f66 100644 --- a/web/themes/manual-dark.css +++ b/web/themes/manual-dark.css @@ -67,4 +67,5 @@ html[data-theme="dark"] { --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%); + --color-pipeline-add-documents-title-bg: linear-gradient(92deg, rgba(54, 191, 250, 1) 0%, rgba(41, 109, 255, 1) 97.78%); } diff --git a/web/themes/manual-light.css b/web/themes/manual-light.css index f2838041c2..fb5cf3f3fe 100644 --- a/web/themes/manual-light.css +++ b/web/themes/manual-light.css @@ -67,4 +67,5 @@ html[data-theme="light"] { --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%); + --color-pipeline-add-documents-title-bg: linear-gradient(92deg, rgba(11, 165, 236, 0.95) 0%, rgba(21, 90, 239, 0.95) 97.78%); }