Chore: frontend infrastructure upgrade (#16420)

Co-authored-by: NFish <douxc512@gmail.com>
Co-authored-by: zxhlyh <jasonapring2015@outlook.com>
Co-authored-by: twwu <twwu@dify.ai>
Co-authored-by: jZonG <jzongcode@gmail.com>
This commit is contained in:
Joel
2025-03-21 17:41:03 +08:00
committed by GitHub
parent e61415223b
commit 7709d9df20
1435 changed files with 13372 additions and 11612 deletions

View File

@@ -2,12 +2,17 @@ import React from 'react'
import MainDetail from '@/app/components/datasets/documents/detail'
export type IDocumentDetailProps = {
params: { datasetId: string; documentId: string }
params: Promise<{ datasetId: string; documentId: string }>
}
const DocumentDetail = async ({
params: { datasetId, documentId },
}: IDocumentDetailProps) => {
const DocumentDetail = async (props: IDocumentDetailProps) => {
const params = await props.params
const {
datasetId,
documentId,
} = params
return (
<MainDetail datasetId={datasetId} documentId={documentId} />
)

View File

@@ -2,12 +2,17 @@ import React from 'react'
import Settings from '@/app/components/datasets/documents/detail/settings'
export type IProps = {
params: { datasetId: string; documentId: string }
params: Promise<{ datasetId: string; documentId: string }>
}
const DocumentSettings = async ({
params: { datasetId, documentId },
}: IProps) => {
const DocumentSettings = async (props: IProps) => {
const params = await props.params
const {
datasetId,
documentId,
} = params
return (
<Settings datasetId={datasetId} documentId={documentId} />
)

View File

@@ -2,12 +2,16 @@ import React from 'react'
import DatasetUpdateForm from '@/app/components/datasets/create'
export type IProps = {
params: { datasetId: string }
params: Promise<{ datasetId: string }>
}
const Create = async ({
params: { datasetId },
}: IProps) => {
const Create = async (props: IProps) => {
const params = await props.params
const {
datasetId,
} = params
return (
<DatasetUpdateForm datasetId={datasetId} />
)

View File

@@ -2,12 +2,16 @@ import React from 'react'
import Main from '@/app/components/datasets/documents'
export type IProps = {
params: { datasetId: string }
params: Promise<{ datasetId: string }>
}
const Documents = async ({
params: { datasetId },
}: IProps) => {
const Documents = async (props: IProps) => {
const params = await props.params
const {
datasetId,
} = params
return (
<Main datasetId={datasetId} />
)

View File

@@ -2,12 +2,16 @@ import React from 'react'
import Main from '@/app/components/datasets/hit-testing'
type Props = {
params: { datasetId: string }
params: Promise<{ datasetId: string }>
}
const HitTesting = ({
params: { datasetId },
}: Props) => {
const HitTesting = async (props: Props) => {
const params = await props.params
const {
datasetId,
} = params
return (
<Main datasetId={datasetId} />
)

View File

@@ -0,0 +1,221 @@
'use client'
import type { FC, SVGProps } from 'react'
import React, { useEffect, useMemo } from 'react'
import { usePathname } from 'next/navigation'
import useSWR from 'swr'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import {
Cog8ToothIcon,
DocumentTextIcon,
PaperClipIcon,
} from '@heroicons/react/24/outline'
import {
Cog8ToothIcon as Cog8ToothSolidIcon,
// CommandLineIcon as CommandLineSolidIcon,
DocumentTextIcon as DocumentTextSolidIcon,
} from '@heroicons/react/24/solid'
import { RiApps2AddLine, RiBookOpenLine, RiInformation2Line } from '@remixicon/react'
import s from './style.module.css'
import classNames from '@/utils/classnames'
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
import type { RelatedAppResponse } from '@/models/datasets'
import AppSideBar from '@/app/components/app-sidebar'
import Loading from '@/app/components/base/loading'
import DatasetDetailContext from '@/context/dataset-detail'
import { DataSourceType } from '@/models/datasets'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { LanguagesSupported } from '@/i18n/language'
import { useStore } from '@/app/components/app/store'
import { getLocaleOnClient } from '@/i18n'
import { useAppContext } from '@/context/app-context'
import Tooltip from '@/app/components/base/tooltip'
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
export type IAppDetailLayoutProps = {
children: React.ReactNode
params: { datasetId: string }
}
const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
<g clipPath="url(#clip0_4610_6951)">
<path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_4610_6951">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
}
const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
<path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" />
<path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" />
<path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" />
</svg>
}
type IExtraInfoProps = {
isMobile: boolean
relatedApps?: RelatedAppResponse
expand: boolean
}
const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
const locale = getLocaleOnClient()
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
const { t } = useTranslation()
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
const relatedAppsTotal = relatedApps?.data?.length || 0
useEffect(() => {
setShowTips(!isMobile)
}, [isMobile, setShowTips])
return <div>
{hasRelatedApps && (
<>
{!isMobile && (
<Tooltip
position='right'
noDecoration
needsDelay
popupContent={
<LinkedAppsPanel
relatedApps={relatedApps.data}
isMobile={isMobile}
/>
}
>
<div className='system-xs-medium-uppercase inline-flex cursor-pointer items-center space-x-1 text-text-secondary'>
<span>{relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')}</span>
<RiInformation2Line className='h-4 w-4' />
</div>
</Tooltip>
)}
{isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
{relatedAppsTotal || '--'}
<PaperClipIcon className='h-4 w-4 text-gray-700' />
</div>}
</>
)}
{!hasRelatedApps && !expand && (
<Tooltip
position='right'
noDecoration
needsDelay
popupContent={
<div className='w-[240px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-4'>
<div className='inline-flex rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle p-2'>
<RiApps2AddLine className='h-4 w-4 text-text-tertiary' />
</div>
<div className='my-2 text-xs text-text-tertiary'>{t('common.datasetMenus.emptyTip')}</div>
<a
className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent'
href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
}
target='_blank' rel='noopener noreferrer'
>
<RiBookOpenLine className='mr-1 text-text-accent' />
{t('common.datasetMenus.viewDoc')}
</a>
</div>
}
>
<div className='system-xs-medium-uppercase inline-flex cursor-pointer items-center space-x-1 text-text-secondary'>
<span>{t('common.datasetMenus.noRelatedApp')}</span>
<RiInformation2Line className='h-4 w-4' />
</div>
</Tooltip>
)}
</div>
}
const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const {
children,
params: { datasetId },
} = props
const pathname = usePathname()
const hideSideBar = /documents\/create$/.test(pathname)
const { t } = useTranslation()
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
url: 'fetchDatasetDetail',
datasetId,
}, apiParams => fetchDatasetDetail(apiParams.datasetId))
const { data: relatedApps } = useSWR({
action: 'fetchDatasetRelatedApps',
datasetId,
}, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
const navigation = useMemo(() => {
const baseNavigation = [
{ name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
// { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
{ name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
]
if (datasetRes?.provider !== 'external') {
baseNavigation.unshift({
name: t('common.datasetMenus.documents'),
href: `/datasets/${datasetId}/documents`,
icon: DocumentTextIcon,
selectedIcon: DocumentTextSolidIcon,
})
}
return baseNavigation
}, [datasetRes?.provider, datasetId, t])
useEffect(() => {
if (datasetRes)
document.title = `${datasetRes.name || 'Dataset'} - Dify`
}, [datasetRes])
const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
useEffect(() => {
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
const mode = isMobile ? 'collapse' : 'expand'
setAppSiderbarExpand(isMobile ? mode : localeMode)
}, [isMobile, setAppSiderbarExpand])
if (!datasetRes && !error)
return <Loading type='app' />
return (
<div className='flex grow overflow-hidden'>
{!hideSideBar && <AppSideBar
title={datasetRes?.name || '--'}
icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
icon_background={datasetRes?.icon_background || '#F5F5F5'}
desc={datasetRes?.description || '--'}
isExternal={datasetRes?.provider === 'external'}
navigation={navigation}
extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} expand={mode === 'collapse'} /> : undefined}
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
/>}
<DatasetDetailContext.Provider value={{
indexingTechnique: datasetRes?.indexing_technique,
dataset: datasetRes,
mutateDatasetRes: () => mutateDatasetRes(),
}}>
<div className="grow overflow-hidden bg-background-default-subtle">{children}</div>
</DatasetDetailContext.Provider>
</div>
)
}
export default React.memo(DatasetDetailLayout)

View File

@@ -1,221 +1,17 @@
'use client'
import type { FC, SVGProps } from 'react'
import React, { useEffect, useMemo } from 'react'
import { usePathname } from 'next/navigation'
import useSWR from 'swr'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import {
Cog8ToothIcon,
DocumentTextIcon,
PaperClipIcon,
} from '@heroicons/react/24/outline'
import {
Cog8ToothIcon as Cog8ToothSolidIcon,
// CommandLineIcon as CommandLineSolidIcon,
DocumentTextIcon as DocumentTextSolidIcon,
} from '@heroicons/react/24/solid'
import { RiApps2AddLine, RiBookOpenLine, RiInformation2Line } from '@remixicon/react'
import s from './style.module.css'
import classNames from '@/utils/classnames'
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
import type { RelatedAppResponse } from '@/models/datasets'
import AppSideBar from '@/app/components/app-sidebar'
import Loading from '@/app/components/base/loading'
import DatasetDetailContext from '@/context/dataset-detail'
import { DataSourceType } from '@/models/datasets'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { LanguagesSupported } from '@/i18n/language'
import { useStore } from '@/app/components/app/store'
import { getLocaleOnClient } from '@/i18n'
import { useAppContext } from '@/context/app-context'
import Tooltip from '@/app/components/base/tooltip'
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
import Main from './layout-main'
export type IAppDetailLayoutProps = {
children: React.ReactNode
params: { datasetId: string }
}
const DatasetDetailLayout = async (
props: {
children: React.ReactNode
params: Promise<{ datasetId: string }>
},
) => {
const params = await props.params
const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
<g clipPath="url(#clip0_4610_6951)">
<path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_4610_6951">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
}
const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
<path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" />
<path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" />
<path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" />
</svg>
}
type IExtraInfoProps = {
isMobile: boolean
relatedApps?: RelatedAppResponse
expand: boolean
}
const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
const locale = getLocaleOnClient()
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
const { t } = useTranslation()
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
const relatedAppsTotal = relatedApps?.data?.length || 0
useEffect(() => {
setShowTips(!isMobile)
}, [isMobile, setShowTips])
return <div>
{hasRelatedApps && (
<>
{!isMobile && (
<Tooltip
position='right'
noDecoration
needsDelay
popupContent={
<LinkedAppsPanel
relatedApps={relatedApps.data}
isMobile={isMobile}
/>
}
>
<div className='inline-flex items-center system-xs-medium-uppercase text-text-secondary space-x-1 cursor-pointer'>
<span>{relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')}</span>
<RiInformation2Line className='w-4 h-4' />
</div>
</Tooltip>
)}
{isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
{relatedAppsTotal || '--'}
<PaperClipIcon className='h-4 w-4 text-gray-700' />
</div>}
</>
)}
{!hasRelatedApps && !expand && (
<Tooltip
position='right'
noDecoration
needsDelay
popupContent={
<div className='p-4 w-[240px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border rounded-xl'>
<div className='inline-flex p-2 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle'>
<RiApps2AddLine className='h-4 w-4 text-text-tertiary' />
</div>
<div className='text-xs text-text-tertiary my-2'>{t('common.datasetMenus.emptyTip')}</div>
<a
className='inline-flex items-center text-xs text-text-accent mt-2 cursor-pointer'
href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
}
target='_blank' rel='noopener noreferrer'
>
<RiBookOpenLine className='mr-1 text-text-accent' />
{t('common.datasetMenus.viewDoc')}
</a>
</div>
}
>
<div className='inline-flex items-center system-xs-medium-uppercase text-text-secondary space-x-1 cursor-pointer'>
<span>{t('common.datasetMenus.noRelatedApp')}</span>
<RiInformation2Line className='w-4 h-4' />
</div>
</Tooltip>
)}
</div>
}
const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const {
children,
params: { datasetId },
} = props
const pathname = usePathname()
const hideSideBar = /documents\/create$/.test(pathname)
const { t } = useTranslation()
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
url: 'fetchDatasetDetail',
datasetId,
}, apiParams => fetchDatasetDetail(apiParams.datasetId))
const { data: relatedApps } = useSWR({
action: 'fetchDatasetRelatedApps',
datasetId,
}, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
const navigation = useMemo(() => {
const baseNavigation = [
{ name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
// { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
{ name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
]
if (datasetRes?.provider !== 'external') {
baseNavigation.unshift({
name: t('common.datasetMenus.documents'),
href: `/datasets/${datasetId}/documents`,
icon: DocumentTextIcon,
selectedIcon: DocumentTextSolidIcon,
})
}
return baseNavigation
}, [datasetRes?.provider, datasetId, t])
useEffect(() => {
if (datasetRes)
document.title = `${datasetRes.name || 'Dataset'} - Dify`
}, [datasetRes])
const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
useEffect(() => {
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
const mode = isMobile ? 'collapse' : 'expand'
setAppSiderbarExpand(isMobile ? mode : localeMode)
}, [isMobile, setAppSiderbarExpand])
if (!datasetRes && !error)
return <Loading type='app' />
return (
<div className='grow flex overflow-hidden'>
{!hideSideBar && <AppSideBar
title={datasetRes?.name || '--'}
icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
icon_background={datasetRes?.icon_background || '#F5F5F5'}
desc={datasetRes?.description || '--'}
isExternal={datasetRes?.provider === 'external'}
navigation={navigation}
extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} expand={mode === 'collapse'} /> : undefined}
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
/>}
<DatasetDetailContext.Provider value={{
indexingTechnique: datasetRes?.indexing_technique,
dataset: datasetRes,
mutateDatasetRes: () => mutateDatasetRes(),
}}>
<div className="bg-background-default-subtle grow overflow-hidden">{children}</div>
</DatasetDetailContext.Provider>
</div>
)
return <Main params={(await params)}>{children}</Main>
}
export default React.memo(DatasetDetailLayout)
export default DatasetDetailLayout

View File

@@ -3,13 +3,13 @@ import { getLocaleOnServer, useTranslation as translate } from '@/i18n/server'
import Form from '@/app/components/datasets/settings/form'
const Settings = async () => {
const locale = getLocaleOnServer()
const locale = await getLocaleOnServer()
const { t } = await translate(locale, 'dataset-settings')
return (
<div className='h-full overflow-y-auto'>
<div className='px-6 py-3'>
<div className='mb-1 system-xl-semibold text-text-primary'>{t('title')}</div>
<div className='system-xl-semibold mb-1 text-text-primary'>{t('title')}</div>
<div className='system-sm-regular text-text-tertiary'>{t('desc')}</div>
</div>
<Form />

View File

@@ -15,22 +15,22 @@ const ApiServer: FC<ApiServerProps> = ({
const { t } = useTranslation()
return (
<div className='flex items-center flex-wrap gap-y-2'>
<div className='flex items-center mr-2 pl-1.5 pr-1 h-8 bg-white/80 border-[0.5px] border-white rounded-lg leading-5'>
<div className='mr-0.5 px-1.5 h-5 border border-gray-200 text-[11px] text-gray-500 rounded-md shrink-0'>{t('appApi.apiServer')}</div>
<div className='px-1 truncate w-fit sm:w-[248px] text-[13px] font-medium text-gray-800'>{apiBaseUrl}</div>
<div className='mx-1 w-[1px] h-[14px] bg-gray-200'></div>
<div className='flex flex-wrap items-center gap-y-2'>
<div className='mr-2 flex h-8 items-center rounded-lg border-[0.5px] border-white bg-white/80 pl-1.5 pr-1 leading-5'>
<div className='mr-0.5 h-5 shrink-0 rounded-md border border-gray-200 px-1.5 text-[11px] text-gray-500'>{t('appApi.apiServer')}</div>
<div className='w-fit truncate px-1 text-[13px] font-medium text-gray-800 sm:w-[248px]'>{apiBaseUrl}</div>
<div className='mx-1 h-[14px] w-[1px] bg-gray-200'></div>
<CopyFeedback
content={apiBaseUrl}
selectorId={randomString(8)}
className={'!w-6 !h-6 hover:bg-gray-200'}
className={'!h-6 !w-6 hover:bg-gray-200'}
/>
</div>
<div className='flex items-center mr-2 px-3 h-8 bg-[#ECFDF3] text-xs font-semibold text-[#039855] rounded-lg border-[0.5px] border-[#D1FADF]'>
<div className='mr-2 flex h-8 items-center rounded-lg border-[0.5px] border-[#D1FADF] bg-[#ECFDF3] px-3 text-xs font-semibold text-[#039855]'>
{t('appApi.ok')}
</div>
<SecretKeyButton
className='shrink-0 !h-8 bg-white'
className='!h-8 shrink-0 bg-white'
/>
</div>
)

View File

@@ -82,8 +82,8 @@ const Container = () => {
}, [currentWorkspace, router])
return (
<div ref={containerRef} className='grow relative flex flex-col bg-background-body overflow-y-auto scroll-container'>
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
<div ref={containerRef} className='scroll-container relative flex grow flex-col overflow-y-auto bg-background-body'>
<div className='sticky top-0 z-10 flex flex-wrap justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'>
<TabSliderNew
value={activeTab}
onChange={newActiveTab => setActiveTab(newActiveTab)}
@@ -108,13 +108,13 @@ const Container = () => {
onChange={e => handleKeywordsChange(e.target.value)}
onClear={() => handleKeywordsChange('')}
/>
<div className="w-[1px] h-4 bg-divider-regular" />
<div className="h-4 w-[1px] bg-divider-regular" />
<Button
className='gap-0.5 shadows-shadow-xs'
className='shadows-shadow-xs gap-0.5'
onClick={() => setShowExternalApiPanel(true)}
>
<ApiConnectionMod className='w-4 h-4 text-components-button-secondary-text' />
<div className='flex px-0.5 justify-center items-center gap-1 text-components-button-secondary-text system-sm-medium'>{t('dataset.externalAPIPanelTitle')}</div>
<ApiConnectionMod className='h-4 w-4 text-components-button-secondary-text' />
<div className='system-sm-medium flex items-center justify-center gap-1 px-0.5 text-components-button-secondary-text'>{t('dataset.externalAPIPanelTitle')}</div>
</Button>
</div>
)}

View File

@@ -84,17 +84,17 @@ const DatasetCard = ({
}
return (
<div className="relative w-full py-1" onMouseLeave={onMouseLeave}>
<div className='h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={onClickRename}>
<span className='text-text-secondary text-sm'>{t('common.operation.settings')}</span>
<div className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickRename}>
<span className='text-sm text-text-secondary'>{t('common.operation.settings')}</span>
</div>
{props.showDelete && (
<>
<Divider className="!my-1" />
<div
className='group h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-destructive-hover rounded-lg cursor-pointer'
className='group mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-destructive-hover'
onClick={onClickDelete}
>
<span className={cn('text-text-secondary text-sm', 'group-hover:text-text-destructive')}>
<span className={cn('text-sm text-text-secondary', 'group-hover:text-text-destructive')}>
{t('common.operation.delete')}
</span>
</div>
@@ -111,7 +111,7 @@ const DatasetCard = ({
return (
<>
<div
className='group relative col-span-1 bg-components-card-bg border-[0.5px] border-solid border-components-card-border rounded-xl shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
className='group relative col-span-1 flex min-h-[160px] cursor-pointer flex-col rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg'
data-disable-nprogress={true}
onClick={(e) => {
e.preventDefault()
@@ -121,25 +121,25 @@ const DatasetCard = ({
}}
>
{isExternalProvider(dataset.provider) && <CornerLabel label='External' className='absolute right-0' labelClassName='rounded-tr-xl' />}
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
<div className='flex h-[66px] shrink-0 grow-0 items-center gap-3 px-[14px] pb-3 pt-[14px]'>
<div className={cn(
'shrink-0 flex items-center justify-center p-2.5 bg-[#F5F8FF] rounded-md border-[0.5px] border-[#E0EAFF]',
'flex shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#E0EAFF] bg-[#F5F8FF] p-2.5',
!dataset.embedding_available && 'opacity-50 hover:opacity-100',
)}>
<Folder className='w-5 h-5 text-[#444CE7]' />
<Folder className='h-5 w-5 text-[#444CE7]' />
</div>
<div className='grow w-0 py-[1px]'>
<div className='flex items-center text-sm leading-5 font-semibold text-text-secondary'>
<div className={cn('truncate', !dataset.embedding_available && 'opacity-50 hover:opacity-100 text-text-tertiary')} title={dataset.name}>{dataset.name}</div>
<div className='w-0 grow py-[1px]'>
<div className='flex items-center text-sm font-semibold leading-5 text-text-secondary'>
<div className={cn('truncate', !dataset.embedding_available && 'text-text-tertiary opacity-50 hover:opacity-100')} title={dataset.name}>{dataset.name}</div>
{!dataset.embedding_available && (
<Tooltip
popupContent={t('dataset.unavailableTip')}
>
<span className='shrink-0 inline-flex w-max ml-1 px-1 border border-divider-regular rounded-md text-text-tertiary text-xs font-normal leading-[18px]'>{t('dataset.unavailable')}</span>
<span className='ml-1 inline-flex w-max shrink-0 rounded-md border border-divider-regular px-1 text-xs font-normal leading-[18px] text-text-tertiary'>{t('dataset.unavailable')}</span>
</Tooltip>
)}
</div>
<div className='flex items-center mt-[1px] text-xs leading-[18px] text-text-tertiary'>
<div className='mt-[1px] flex items-center text-xs leading-[18px] text-text-tertiary'>
<div
className={cn('truncate', (!dataset.embedding_available || !dataset.document_count) && 'opacity-50')}
title={dataset.provider === 'external' ? `${dataset.app_count}${t('dataset.appCount')}` : `${dataset.document_count}${t('dataset.documentCount')} · ${Math.round(dataset.word_count / 1000)}${t('dataset.wordCount')} · ${dataset.app_count}${t('dataset.appCount')}`}
@@ -150,9 +150,9 @@ const DatasetCard = ({
</>
: <>
<span>{dataset.document_count}{t('dataset.documentCount')}</span>
<span className='shrink-0 mx-0.5 w-1 text-text-tertiary'>·</span>
<span className='mx-0.5 w-1 shrink-0 text-text-tertiary'>·</span>
<span>{Math.round(dataset.word_count / 1000)}{t('dataset.wordCount')}</span>
<span className='shrink-0 mx-0.5 w-1 text-text-tertiary'>·</span>
<span className='mx-0.5 w-1 shrink-0 text-text-tertiary'>·</span>
<span>{dataset.app_count}{t('dataset.appCount')}</span>
</>
}
@@ -162,7 +162,7 @@ const DatasetCard = ({
</div>
<div
className={cn(
'grow mb-2 px-[14px] max-h-[72px] text-xs leading-normal text-text-tertiary group-hover:line-clamp-2 group-hover:max-h-[36px]',
'mb-2 max-h-[72px] grow px-[14px] text-xs leading-normal text-text-tertiary group-hover:line-clamp-2 group-hover:max-h-[36px]',
tags.length ? 'line-clamp-2' : 'line-clamp-4',
!dataset.embedding_available && 'opacity-50 hover:opacity-100',
)}
@@ -170,15 +170,15 @@ const DatasetCard = ({
{dataset.description}
</div>
<div className={cn(
'items-center shrink-0 mt-1 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
'mt-1 h-[42px] shrink-0 items-center pb-[6px] pl-[14px] pr-[6px] pt-1',
tags.length ? 'flex' : '!hidden group-hover:!flex',
)}>
<div className={cn('grow flex items-center gap-1 w-0', !dataset.embedding_available && 'opacity-50 hover:opacity-100')} onClick={(e) => {
<div className={cn('flex w-0 grow items-center gap-1', !dataset.embedding_available && 'opacity-50 hover:opacity-100')} onClick={(e) => {
e.stopPropagation()
e.preventDefault()
}}>
<div className={cn(
'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full',
'mr-[41px] w-full grow group-hover:!mr-0 group-hover:!block',
tags.length ? '!block' : '!hidden',
)}>
<TagSelector
@@ -192,26 +192,26 @@ const DatasetCard = ({
/>
</div>
</div>
<div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px] bg-divider-regular' />
<div className='!hidden group-hover:!flex shrink-0'>
<div className='mx-1 !hidden h-[14px] w-[1px] shrink-0 bg-divider-regular group-hover:!flex' />
<div className='!hidden shrink-0 group-hover:!flex'>
<CustomPopover
htmlContent={<Operations showDelete={!isCurrentWorkspaceDatasetOperator} />}
position="br"
trigger="click"
btnElement={
<div
className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md'
className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-md'
>
<RiMoreFill className='w-4 h-4 text-text-secondary' />
<RiMoreFill className='h-4 w-4 text-text-secondary' />
</div>
}
btnClassName={open =>
cn(
open ? '!bg-black/5 !shadow-none' : '!bg-transparent',
'h-8 w-8 !p-2 rounded-md border-none hover:!bg-black/5',
'h-8 w-8 rounded-md border-none !p-2 hover:!bg-black/5',
)
}
className={'!w-[128px] h-fit !z-20'}
className={'!z-20 h-fit !w-[128px]'}
/>
</div>
</div>

View File

@@ -6,8 +6,8 @@ const DatasetFooter = () => {
const { t } = useTranslation()
return (
<footer className='px-12 py-6 grow-0 shrink-0'>
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('dataset.didYouKnow')}</h3>
<footer className='shrink-0 grow-0 px-12 py-6'>
<h3 className='text-gradient text-xl font-semibold leading-tight'>{t('dataset.didYouKnow')}</h3>
<p className='mt-1 text-sm font-normal leading-tight text-text-secondary'>
{t('dataset.intro1')}<span className='inline-flex items-center gap-1 text-text-accent'>{t('dataset.intro2')}</span>{t('dataset.intro3')}<br />
{t('dataset.intro4')}<span className='inline-flex items-center gap-1 text-text-accent'>{t('dataset.intro5')}</span>{t('dataset.intro6')}

View File

@@ -86,7 +86,7 @@ const Datasets = ({
}, [onScroll])
return (
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
<nav className='grid shrink-0 grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
{ isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} /> }
{data?.map(({ data: datasets }) => datasets.map(dataset => (
<DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />),

View File

@@ -71,8 +71,8 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
<div className={`fixed right-20 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
{isTocExpanded
? (
<nav className="toc w-full bg-gray-50 p-4 rounded-lg shadow-md max-h-[calc(100vh-150px)] overflow-y-auto">
<div className="flex justify-between items-center mb-4">
<nav className="toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg bg-gray-50 p-4 shadow-md">
<div className="mb-4 flex items-center justify-between">
<h3 className="text-lg font-semibold">{t('appApi.develop.toc')}</h3>
<button
onClick={() => setIsTocExpanded(false)}
@@ -86,7 +86,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
<li key={index}>
<a
href={item.href}
className="text-gray-600 hover:text-gray-900 hover:underline transition-colors duration-200"
className="text-gray-600 transition-colors duration-200 hover:text-gray-900 hover:underline"
onClick={e => handleTocClick(e, item)}
>
{item.text}
@@ -99,13 +99,13 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
: (
<button
onClick={() => setIsTocExpanded(true)}
className="w-10 h-10 bg-gray-50 rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200"
className="flex h-10 w-10 items-center justify-center rounded-full bg-gray-50 shadow-md transition-colors duration-200 hover:bg-gray-100"
>
<RiListUnordered className="w-6 h-6" />
<RiListUnordered className="h-6 w-6" />
</button>
)}
</div>
<article className='mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl'>
<article className='prose-xl prose mx-1 rounded-t-xl bg-white px-4 pt-16 sm:mx-12'>
{locale !== LanguagesSupported[1]
? <TemplateEn apiBaseUrl={apiBaseUrl} />
: <TemplateZh apiBaseUrl={apiBaseUrl} />

View File

@@ -1,37 +1,40 @@
'use client'
import { forwardRef } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiAddLine,
RiArrowRightLine,
} from '@remixicon/react'
const CreateAppCard = forwardRef<HTMLAnchorElement>((_, ref) => {
const CreateAppCard = (
{
ref,
..._
},
) => {
const { t } = useTranslation()
return (
<div className='flex flex-col bg-background-default-dimm border-[0.5px] border-components-panel-border rounded-xl
min-h-[160px] transition-all duration-200 ease-in-out'
<div className='bg-background-default-dimm flex min-h-[160px] flex-col rounded-xl border-[0.5px]
border-components-panel-border transition-all duration-200 ease-in-out'
>
<a ref={ref} className='group flex flex-grow items-start p-4 cursor-pointer' href='/datasets/create'>
<a ref={ref} className='group flex grow cursor-pointer items-start p-4' href='/datasets/create'>
<div className='flex items-center gap-3'>
<div className='w-10 h-10 p-2 flex items-center justify-center border border-dashed border-divider-regular rounded-lg
bg-background-default-lighter group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge'
<div className='flex h-10 w-10 items-center justify-center rounded-lg border border-dashed border-divider-regular bg-background-default-lighter
p-2 group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge'
>
<RiAddLine className='w-4 h-4 text-text-tertiary group-hover:text-text-accent'/>
<RiAddLine className='h-4 w-4 text-text-tertiary group-hover:text-text-accent'/>
</div>
<div className='system-md-semibold text-text-secondary group-hover:text-text-accent'>{t('dataset.createDataset')}</div>
</div>
</a>
<div className='p-4 pt-0 text-text-tertiary system-xs-regular'>{t('dataset.createDatasetIntro')}</div>
<a className='group flex p-4 items-center gap-1 border-t-[0.5px] border-divider-subtle rounded-b-xl cursor-pointer' href='/datasets/connect'>
<div className='system-xs-regular p-4 pt-0 text-text-tertiary'>{t('dataset.createDatasetIntro')}</div>
<a className='group flex cursor-pointer items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle p-4' href='/datasets/connect'>
<div className='system-xs-medium text-text-tertiary group-hover:text-text-accent'>{t('dataset.connectDataset')}</div>
<RiArrowRightLine className='w-3.5 h-3.5 text-text-tertiary group-hover:text-text-accent' />
<RiArrowRightLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-accent' />
</a>
</div>
)
})
}
CreateAppCard.displayName = 'CreateAppCard'