Feat/install process refinement (#3982)

This commit is contained in:
crazywoola 2024-04-29 17:55:52 +08:00 committed by GitHub
parent 1d432728ac
commit c5e2659771
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 871 additions and 840 deletions

View File

@ -1,248 +1,248 @@
'use client'
import type { FC, SVGProps } from 'react'
import React, { useEffect } from 'react'
import { usePathname } from 'next/navigation'
import useSWR from 'swr'
import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
import { useBoolean } from 'ahooks'
import {
Cog8ToothIcon,
// CommandLineIcon,
Squares2X2Icon,
// eslint-disable-next-line sort-imports
PuzzlePieceIcon,
DocumentTextIcon,
PaperClipIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/24/outline'
import {
Cog8ToothIcon as Cog8ToothSolidIcon,
// CommandLineIcon as CommandLineSolidIcon,
DocumentTextIcon as DocumentTextSolidIcon,
} from '@heroicons/react/24/solid'
import Link from 'next/link'
import s from './style.module.css'
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
import { getLocaleOnClient } from '@/i18n'
import AppSideBar from '@/app/components/app-sidebar'
import Divider from '@/app/components/base/divider'
import AppIcon from '@/app/components/base/app-icon'
import Loading from '@/app/components/base/loading'
import FloatPopoverContainer from '@/app/components/base/float-popover-container'
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 { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
export type IAppDetailLayoutProps = {
children: React.ReactNode
params: { datasetId: string }
}
type ILikedItemProps = {
type?: 'plugin' | 'app'
appStatus?: boolean
detail: RelatedApp
isMobile: boolean
}
const LikedItem = ({
type = 'app',
detail,
isMobile,
}: ILikedItemProps) => {
return (
<Link className={classNames(s.itemWrapper, 'px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
<div className={classNames(s.iconWrapper, 'mr-0')}>
<AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background} />
{type === 'app' && (
<span className='absolute bottom-[-2px] right-[-2px] w-3.5 h-3.5 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
{detail.mode === 'advanced-chat' && (
<ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
)}
{detail.mode === 'agent-chat' && (
<CuteRobote className='w-2.5 h-2.5 text-indigo-600' />
)}
{detail.mode === 'chat' && (
<ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
)}
{detail.mode === 'completion' && (
<AiText className='w-2.5 h-2.5 text-[#0E9384]' />
)}
{detail.mode === 'workflow' && (
<Route className='w-2.5 h-2.5 text-[#f79009]' />
)}
</span>
)}
</div>
{!isMobile && <div className={classNames(s.appInfo, 'ml-2')}>{detail?.name || '--'}</div>}
</Link>
)
}
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>
}
const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
<path opacity="0.12" d="M1 3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7V10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1Z" fill="#155EEF" />
<path d="M6 10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7M6 10.5V4.7M6 10.5L6.05003 10.425C6.39735 9.90398 6.57101 9.64349 6.80045 9.45491C7.00357 9.28796 7.23762 9.1627 7.4892 9.0863C7.77337 9 8.08645 9 8.71259 9H9.4C9.96005 9 10.2401 9 10.454 8.89101C10.6422 8.79513 10.7951 8.64215 10.891 8.45399C11 8.24008 11 7.96005 11 7.4V3.1C11 2.53995 11 2.25992 10.891 2.04601C10.7951 1.85785 10.6422 1.70487 10.454 1.60899C10.2401 1.5 9.96005 1.5 9.4 1.5H9.2C8.07989 1.5 7.51984 1.5 7.09202 1.71799C6.71569 1.90973 6.40973 2.21569 6.21799 2.59202C6 3.01984 6 3.5799 6 4.7" stroke="#155EEF" strokeLinecap="round" strokeLinejoin="round" />
</svg>
}
type IExtraInfoProps = {
isMobile: boolean
relatedApps?: RelatedAppResponse
}
const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
const locale = getLocaleOnClient()
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
const { t } = useTranslation()
useEffect(() => {
setShowTips(!isMobile)
}, [isMobile, setShowTips])
return <div className='w-full flex flex-col items-center'>
<Divider className='mt-5' />
{(relatedApps?.data && relatedApps?.data?.length > 0) && (
<>
{!isMobile && <div className='w-full px-2 pb-1 pt-4 uppercase text-xs text-gray-500 font-medium'>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>}
{isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
{relatedApps?.total || '--'}
<PaperClipIcon className='h-4 w-4 text-gray-700' />
</div>}
{relatedApps?.data?.map((item, index) => (<LikedItem key={index} isMobile={isMobile} detail={item} />))}
</>
)}
{!relatedApps?.data?.length && (
<FloatPopoverContainer
placement='bottom-start'
open={isShowTips}
toggle={toggleTips}
isMobile={isMobile}
triggerElement={
<div className={classNames('h-7 w-7 inline-flex justify-center items-center rounded-lg bg-transparent', isShowTips && '!bg-gray-50')}>
<QuestionMarkCircleIcon className='h-4 w-4 flex-shrink-0 text-gray-500' />
</div>
}
>
<div className={classNames('mt-5 p-3', isMobile && 'border-[0.5px] border-gray-200 shadow-lg rounded-lg bg-white w-[160px]')}>
<div className='flex items-center justify-start gap-2'>
<div className={s.emptyIconDiv}>
<Squares2X2Icon className='w-3 h-3 text-gray-500' />
</div>
<div className={s.emptyIconDiv}>
<PuzzlePieceIcon className='w-3 h-3 text-gray-500' />
</div>
</div>
<div className='text-xs text-gray-500 mt-2'>{t('common.datasetMenus.emptyTip')}</div>
<a
className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
: 'https://docs.dify.ai/user-guide/creating-dify-apps/prompt-engineering'
}
target='_blank' rel='noopener noreferrer'
>
<BookOpenIcon className='mr-1' />
{t('common.datasetMenus.viewDoc')}
</a>
</div>
</FloatPopoverContainer>
)}
</div>
}
const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const {
children,
params: { datasetId },
} = props
const pathname = usePathname()
const hideSideBar = /documents\/create$/.test(pathname)
const { t } = useTranslation()
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 = [
{ name: t('common.datasetMenus.documents'), href: `/datasets/${datasetId}/documents`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
{ 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 },
]
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 />
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 || '--'}
navigation={navigation}
extraInfo={mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} />}
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
/>}
<DatasetDetailContext.Provider value={{
indexingTechnique: datasetRes?.indexing_technique,
dataset: datasetRes,
mutateDatasetRes: () => mutateDatasetRes(),
}}>
<div className="bg-white grow overflow-hidden">{children}</div>
</DatasetDetailContext.Provider>
</div>
)
}
export default React.memo(DatasetDetailLayout)
'use client'
import type { FC, SVGProps } from 'react'
import React, { useEffect } from 'react'
import { usePathname } from 'next/navigation'
import useSWR from 'swr'
import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
import { useBoolean } from 'ahooks'
import {
Cog8ToothIcon,
// CommandLineIcon,
Squares2X2Icon,
// eslint-disable-next-line sort-imports
PuzzlePieceIcon,
DocumentTextIcon,
PaperClipIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/24/outline'
import {
Cog8ToothIcon as Cog8ToothSolidIcon,
// CommandLineIcon as CommandLineSolidIcon,
DocumentTextIcon as DocumentTextSolidIcon,
} from '@heroicons/react/24/solid'
import Link from 'next/link'
import s from './style.module.css'
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
import AppSideBar from '@/app/components/app-sidebar'
import Divider from '@/app/components/base/divider'
import AppIcon from '@/app/components/base/app-icon'
import Loading from '@/app/components/base/loading'
import FloatPopoverContainer from '@/app/components/base/float-popover-container'
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 { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
import { getLocaleOnServer } from '@/i18n/server'
export type IAppDetailLayoutProps = {
children: React.ReactNode
params: { datasetId: string }
}
type ILikedItemProps = {
type?: 'plugin' | 'app'
appStatus?: boolean
detail: RelatedApp
isMobile: boolean
}
const LikedItem = ({
type = 'app',
detail,
isMobile,
}: ILikedItemProps) => {
return (
<Link className={classNames(s.itemWrapper, 'px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
<div className={classNames(s.iconWrapper, 'mr-0')}>
<AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background} />
{type === 'app' && (
<span className='absolute bottom-[-2px] right-[-2px] w-3.5 h-3.5 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
{detail.mode === 'advanced-chat' && (
<ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
)}
{detail.mode === 'agent-chat' && (
<CuteRobote className='w-2.5 h-2.5 text-indigo-600' />
)}
{detail.mode === 'chat' && (
<ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
)}
{detail.mode === 'completion' && (
<AiText className='w-2.5 h-2.5 text-[#0E9384]' />
)}
{detail.mode === 'workflow' && (
<Route className='w-2.5 h-2.5 text-[#f79009]' />
)}
</span>
)}
</div>
{!isMobile && <div className={classNames(s.appInfo, 'ml-2')}>{detail?.name || '--'}</div>}
</Link>
)
}
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>
}
const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
<path opacity="0.12" d="M1 3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7V10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1Z" fill="#155EEF" />
<path d="M6 10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7M6 10.5V4.7M6 10.5L6.05003 10.425C6.39735 9.90398 6.57101 9.64349 6.80045 9.45491C7.00357 9.28796 7.23762 9.1627 7.4892 9.0863C7.77337 9 8.08645 9 8.71259 9H9.4C9.96005 9 10.2401 9 10.454 8.89101C10.6422 8.79513 10.7951 8.64215 10.891 8.45399C11 8.24008 11 7.96005 11 7.4V3.1C11 2.53995 11 2.25992 10.891 2.04601C10.7951 1.85785 10.6422 1.70487 10.454 1.60899C10.2401 1.5 9.96005 1.5 9.4 1.5H9.2C8.07989 1.5 7.51984 1.5 7.09202 1.71799C6.71569 1.90973 6.40973 2.21569 6.21799 2.59202C6 3.01984 6 3.5799 6 4.7" stroke="#155EEF" strokeLinecap="round" strokeLinejoin="round" />
</svg>
}
type IExtraInfoProps = {
isMobile: boolean
relatedApps?: RelatedAppResponse
}
const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
const locale = getLocaleOnServer()
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
const { t } = useTranslation()
useEffect(() => {
setShowTips(!isMobile)
}, [isMobile, setShowTips])
return <div className='w-full flex flex-col items-center'>
<Divider className='mt-5' />
{(relatedApps?.data && relatedApps?.data?.length > 0) && (
<>
{!isMobile && <div className='w-full px-2 pb-1 pt-4 uppercase text-xs text-gray-500 font-medium'>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>}
{isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
{relatedApps?.total || '--'}
<PaperClipIcon className='h-4 w-4 text-gray-700' />
</div>}
{relatedApps?.data?.map((item, index) => (<LikedItem key={index} isMobile={isMobile} detail={item} />))}
</>
)}
{!relatedApps?.data?.length && (
<FloatPopoverContainer
placement='bottom-start'
open={isShowTips}
toggle={toggleTips}
isMobile={isMobile}
triggerElement={
<div className={classNames('h-7 w-7 inline-flex justify-center items-center rounded-lg bg-transparent', isShowTips && '!bg-gray-50')}>
<QuestionMarkCircleIcon className='h-4 w-4 flex-shrink-0 text-gray-500' />
</div>
}
>
<div className={classNames('mt-5 p-3', isMobile && 'border-[0.5px] border-gray-200 shadow-lg rounded-lg bg-white w-[160px]')}>
<div className='flex items-center justify-start gap-2'>
<div className={s.emptyIconDiv}>
<Squares2X2Icon className='w-3 h-3 text-gray-500' />
</div>
<div className={s.emptyIconDiv}>
<PuzzlePieceIcon className='w-3 h-3 text-gray-500' />
</div>
</div>
<div className='text-xs text-gray-500 mt-2'>{t('common.datasetMenus.emptyTip')}</div>
<a
className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
: 'https://docs.dify.ai/user-guide/creating-dify-apps/prompt-engineering'
}
target='_blank' rel='noopener noreferrer'
>
<BookOpenIcon className='mr-1' />
{t('common.datasetMenus.viewDoc')}
</a>
</div>
</FloatPopoverContainer>
)}
</div>
}
const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const {
children,
params: { datasetId },
} = props
const pathname = usePathname()
const hideSideBar = /documents\/create$/.test(pathname)
const { t } = useTranslation()
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 = [
{ name: t('common.datasetMenus.documents'), href: `/datasets/${datasetId}/documents`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
{ 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 },
]
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 />
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 || '--'}
navigation={navigation}
extraInfo={mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} />}
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
/>}
<DatasetDetailContext.Provider value={{
indexingTechnique: datasetRes?.indexing_technique,
dataset: datasetRes,
mutateDatasetRes: () => mutateDatasetRes(),
}}>
<div className="bg-white grow overflow-hidden">{children}</div>
</DatasetDetailContext.Provider>
</div>
)
}
export default React.memo(DatasetDetailLayout)

View File

@ -86,7 +86,7 @@ const ActivateForm = () => {
timezone,
},
})
setLocaleOnClient(language.startsWith('en') ? 'en' : 'zh-Hans', false)
setLocaleOnClient(language.startsWith('en') ? 'en-US' : 'zh-Hans', false)
setShowSuccess(true)
}
catch {

View File

@ -60,7 +60,7 @@ const EnterpriseSSOForm: FC<EnterpriseSSOFormProps> = ({
return (
<div className={
cn(
'flex flex-col items-center w-full grow items-center justify-center',
'flex flex-col items-center w-full grow justify-center',
'px-6',
'md:px-[108px]',
)

View File

@ -20,7 +20,7 @@ const Forms = () => {
}
return <div className={
cn(
'flex flex-col items-center w-full grow items-center justify-center',
'flex flex-col items-center w-full grow justify-center',
'px-6',
'md:px-[108px]',
)

View File

@ -245,7 +245,7 @@ const NormalForm = () => {
<span className='cursor-pointer text-primary-600'>{t('login.forget')}</span>
</Tooltip> */}
</label>
<div className="relative mt-1 rounded-md shadow-sm">
<div className="relative mt-1">
<input
id="password"
value={password}
@ -300,6 +300,15 @@ const NormalForm = () => {
>{t('login.pp')}</Link>
</div>
{IS_CE_EDITION && <div className="w-hull text-center block mt-2 text-xs text-gray-600">
{t('login.goToInit')}
&nbsp;
<Link
className='text-primary-600'
href='/install'
>{t('login.setAdminAccount')}</Link>
</div>}
</div>
</div>
</>

View File

@ -61,7 +61,7 @@ gtag('config', 'AW-11217955271"');
{loading && (
<div className={
cn(
'flex flex-col items-center w-full grow items-center justify-center',
'flex flex-col items-center w-full grow justify-center',
'px-6',
'md:px-[108px]',
)

View File

@ -8,13 +8,13 @@ import { getLanguage } from '@/i18n/language'
type II18NContext = {
locale: Locale
i18n: Record<string, any>
setLocaleOnClient: (locale: Locale, reloadPage?: boolean) => void
setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => void
}
const I18NContext = createContext<II18NContext>({
locale: 'en-US',
i18n: {},
setLocaleOnClient: (lang: Locale, reloadPage?: boolean) => { },
setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => { },
})
export const useI18N = () => useContext(I18NContext)

File diff suppressed because it is too large Load Diff

View File

@ -1,59 +1,60 @@
const translation = {
pageTitle: 'Hey, lass uns anfangen!👋',
welcome: 'Willkommen bei Dify, bitte melde dich an, um fortzufahren.',
email: 'E-Mail-Adresse',
emailPlaceholder: 'Deine E-Mail',
password: 'Passwort',
passwordPlaceholder: 'Dein Passwort',
name: 'Benutzername',
namePlaceholder: 'Dein Benutzername',
forget: 'Passwort vergessen?',
signBtn: 'Anmelden',
installBtn: 'Einrichten',
setAdminAccount: 'Admin-Konto einrichten',
setAdminAccountDesc: 'Maximale Berechtigungen für das Admin-Konto, das verwendet werden kann, um Anwendungen zu erstellen und LLM-Anbieter usw. zu verwalten.',
createAndSignIn: 'Erstellen und anmelden',
oneMoreStep: 'Nur noch ein Schritt',
createSample: 'Basierend auf diesen Informationen erstellen wir eine Beispielanwendung für dich',
invitationCode: 'Einladungscode',
invitationCodePlaceholder: 'Dein Einladungscode',
interfaceLanguage: 'Oberflächensprache',
timezone: 'Zeitzone',
go: 'Zu Dify gehen',
sendUsMail: 'Sende uns deine Vorstellung per E-Mail, und wir bearbeiten die Einladungsanfrage.',
acceptPP: 'Ich habe die Datenschutzbestimmungen gelesen und akzeptiere sie',
reset: 'Bitte führe den folgenden Befehl aus, um dein Passwort zurückzusetzen',
withGitHub: 'Mit GitHub fortfahren',
withGoogle: 'Mit Google fortfahren',
rightTitle: 'Das volle Potenzial von LLM ausschöpfen',
rightDesc: 'Mühelos optisch ansprechende, bedienbare und verbesserbare KI-Anwendungen erstellen.',
tos: 'Nutzungsbedingungen',
pp: 'Datenschutzbestimmungen',
tosDesc: 'Mit der Anmeldung stimmst du unseren',
donthave: 'Hast du nicht?',
invalidInvitationCode: 'Ungültiger Einladungscode',
accountAlreadyInited: 'Konto bereits initialisiert',
error: {
emailEmpty: 'E-Mail-Adresse wird benötigt',
emailInValid: 'Bitte gib eine gültige E-Mail-Adresse ein',
nameEmpty: 'Name wird benötigt',
passwordEmpty: 'Passwort wird benötigt',
passwordInvalid: 'Das Passwort muss Buchstaben und Zahlen enthalten und länger als 8 Zeichen sein',
},
license: {
tip: 'Bevor du mit Dify Community Edition beginnst, lies die',
link: 'Open-Source-Lizenz',
},
join: 'Beitreten',
joinTipStart: 'Lade dich ein, dem',
joinTipEnd: 'Team auf Dify beizutreten',
invalid: 'Der Link ist abgelaufen',
explore: 'Dify erkunden',
activatedTipStart: 'Du bist dem',
activatedTipEnd: 'Team beigetreten',
activated: 'Jetzt anmelden',
adminInitPassword: 'Admin-Initialpasswort',
validate: 'Validieren',
}
export default translation
const translation = {
pageTitle: 'Hey, lass uns anfangen!👋',
welcome: 'Willkommen bei Dify, bitte melde dich an, um fortzufahren.',
email: 'E-Mail-Adresse',
emailPlaceholder: 'Deine E-Mail',
password: 'Passwort',
passwordPlaceholder: 'Dein Passwort',
name: 'Benutzername',
namePlaceholder: 'Dein Benutzername',
forget: 'Passwort vergessen?',
signBtn: 'Anmelden',
installBtn: 'Einrichten',
setAdminAccount: 'Admin-Konto einrichten',
setAdminAccountDesc: 'Maximale Berechtigungen für das Admin-Konto, das verwendet werden kann, um Anwendungen zu erstellen und LLM-Anbieter usw. zu verwalten.',
createAndSignIn: 'Erstellen und anmelden',
oneMoreStep: 'Nur noch ein Schritt',
createSample: 'Basierend auf diesen Informationen erstellen wir eine Beispielanwendung für dich',
invitationCode: 'Einladungscode',
invitationCodePlaceholder: 'Dein Einladungscode',
interfaceLanguage: 'Oberflächensprache',
timezone: 'Zeitzone',
go: 'Zu Dify gehen',
sendUsMail: 'Sende uns deine Vorstellung per E-Mail, und wir bearbeiten die Einladungsanfrage.',
acceptPP: 'Ich habe die Datenschutzbestimmungen gelesen und akzeptiere sie',
reset: 'Bitte führe den folgenden Befehl aus, um dein Passwort zurückzusetzen',
withGitHub: 'Mit GitHub fortfahren',
withGoogle: 'Mit Google fortfahren',
rightTitle: 'Das volle Potenzial von LLM ausschöpfen',
rightDesc: 'Mühelos optisch ansprechende, bedienbare und verbesserbare KI-Anwendungen erstellen.',
tos: 'Nutzungsbedingungen',
pp: 'Datenschutzbestimmungen',
tosDesc: 'Mit der Anmeldung stimmst du unseren',
goToInit: 'Wenn du das Konto noch nicht initialisiert hast, gehe bitte zur Initialisierungsseite',
donthave: 'Hast du nicht?',
invalidInvitationCode: 'Ungültiger Einladungscode',
accountAlreadyInited: 'Konto bereits initialisiert',
error: {
emailEmpty: 'E-Mail-Adresse wird benötigt',
emailInValid: 'Bitte gib eine gültige E-Mail-Adresse ein',
nameEmpty: 'Name wird benötigt',
passwordEmpty: 'Passwort wird benötigt',
passwordInvalid: 'Das Passwort muss Buchstaben und Zahlen enthalten und länger als 8 Zeichen sein',
},
license: {
tip: 'Bevor du mit Dify Community Edition beginnst, lies die',
link: 'Open-Source-Lizenz',
},
join: 'Beitreten',
joinTipStart: 'Lade dich ein, dem',
joinTipEnd: 'Team auf Dify beizutreten',
invalid: 'Der Link ist abgelaufen',
explore: 'Dify erkunden',
activatedTipStart: 'Du bist dem',
activatedTipEnd: 'Team beigetreten',
activated: 'Jetzt anmelden',
adminInitPassword: 'Admin-Initialpasswort',
validate: 'Validieren',
}
export default translation

View File

@ -44,6 +44,7 @@ const translation = {
voice: {
language: {
zhHans: 'Chinese',
zhHant: 'Traditional Chinese',
enUS: 'English',
deDE: 'German',
frFR: 'French',
@ -56,6 +57,7 @@ const translation = {
ptBR: 'Portuguese',
ruRU: 'Russian',
ukUA: 'Ukrainian',
viVN: 'Vietnamese',
},
},
unit: {

View File

@ -31,6 +31,7 @@ const translation = {
tos: 'Terms of Service',
pp: 'Privacy Policy',
tosDesc: 'By signing up, you agree to our',
goToInit: 'If you have not initialized the account, please go to the initialization page',
donthave: 'Don\'t have?',
invalidInvitationCode: 'Invalid invitation code',
accountAlreadyInited: 'Account already initialized',

View File

@ -44,6 +44,7 @@ const translation = {
voice: {
language: {
zhHans: 'Chinois',
zhHant: 'Chinois (traditionnel)',
enUS: 'Anglais',
deDE: 'Allemand',
frFR: 'Français',
@ -56,6 +57,7 @@ const translation = {
ptBR: 'Portugais',
ruRU: 'Russe',
ukUA: 'Ukrainien',
viVN: 'Vietnamien',
},
},
unit: {

View File

@ -30,6 +30,7 @@ const translation = {
tos: 'Conditions de Service',
pp: 'Politique de Confidentialité',
tosDesc: 'En vous inscrivant, vous acceptez nos',
goToInit: 'Si vous n\'avez pas initialisé le compte, veuillez vous rendre sur la page d\'initialisation',
donthave: 'Vous n\'avez pas ?',
invalidInvitationCode: 'Code d\'invitation invalide',
accountAlreadyInited: 'Compte déjà initialisé',

View File

@ -11,10 +11,6 @@ export const i18n = {
export type Locale = typeof i18n['locales'][number]
export const getLocaleOnClient = (): Locale => {
return Cookies.get(LOCALE_COOKIE_NAME) as Locale || i18n.defaultLocale
}
export const setLocaleOnClient = (locale: Locale, reloadPage = true) => {
Cookies.set(LOCALE_COOKIE_NAME, locale)
changeLanguage(locale)

View File

@ -44,6 +44,7 @@ const translation = {
voice: {
language: {
zhHans: '中国語',
zhHant: '繁体字中国語',
enUS: '英語',
deDE: 'ドイツ語',
frFR: 'フランス語',
@ -56,6 +57,7 @@ const translation = {
ptBR: 'ポルトガル語',
ruRU: 'ロシア語',
ukUA: 'ウクライナ語',
viVN: 'ベトナム語',
},
},
unit: {

View File

@ -30,6 +30,7 @@ const translation = {
tos: '利用規約',
pp: 'プライバシーポリシー',
tosDesc: 'サインアップすることで、以下に同意するものとします',
goToInit: 'アカウントを初期化していない場合は、初期化ページに移動してください',
donthave: 'お持ちでない場合',
invalidInvitationCode: '無効な招待コード',
accountAlreadyInited: 'アカウントは既に初期化されています',

View File

@ -44,6 +44,7 @@ const translation = {
voice: {
language: {
zhHans: 'Chinês',
zhHant: 'Chinês Tradicional',
enUS: 'Inglês',
deDE: 'Alemão',
frFR: 'Francês',
@ -56,6 +57,7 @@ const translation = {
ptBR: 'Português',
ruRU: 'Russo',
ukUA: 'Ucraniano',
viVN: 'Vietnamita',
},
},
unit: {

View File

@ -30,6 +30,7 @@ const translation = {
tos: 'Termos de Serviço',
pp: 'Política de Privacidade',
tosDesc: 'Ao se inscrever, você concorda com nossos',
goToInit: 'Se você não inicializou a conta, vá para a página de inicialização',
donthave: 'Não tem?',
invalidInvitationCode: 'Código de convite inválido',
accountAlreadyInited: 'Conta já iniciada',

View File

@ -44,6 +44,7 @@ const translation = {
voice: {
language: {
zhHans: 'Китайська',
zhHant: 'Китайська (традиційна)',
enUS: 'Англійська',
deDE: 'Німецька',
frFR: 'Французька',
@ -56,6 +57,7 @@ const translation = {
ptBR: 'Португальська',
ruRU: 'Російська',
ukUA: 'Українська',
viVN: 'В\'є тнамська',
},
},
unit: {

View File

@ -30,6 +30,7 @@ const translation = {
tos: 'Умови обслуговування',
pp: 'Політика конфіденційності',
tosDesc: 'Реєструючись, ви приймаєте наші',
goToInit: 'Якщо ви ще не ініціалізували обліковий запис, перейдіть на сторінку ініціалізації',
donthave: 'Не маєте?',
invalidInvitationCode: 'Недійсний код запрошення',
accountAlreadyInited: 'Обліковий запис уже ініціалізовано',

View File

@ -56,6 +56,7 @@ const translation = {
ptBR: 'Tiếng Bồ Đào Nha',
ruRU: 'Tiếng Nga',
ukUA: 'Tiếng Ukraina',
viVN: 'Tiếng Việt',
},
},
unit: {

View File

@ -30,6 +30,7 @@ const translation = {
tos: 'Điều khoản dịch vụ',
pp: 'Chính sách bảo mật',
tosDesc: 'Bằng cách đăng ký, bạn đồng ý với',
goToInit: 'Nếu bạn chưa khởi tạo tài khoản, vui lòng đi đến trang khởi tạo',
donthave: 'Chưa có?',
invalidInvitationCode: 'Mã mời không hợp lệ',
accountAlreadyInited: 'Tài khoản đã được khởi tạo',

View File

@ -44,6 +44,7 @@ const translation = {
voice: {
language: {
zhHans: '中文',
zhHant: '繁体中文',
enUS: '英语',
deDE: '德语',
frFR: '法语',
@ -56,6 +57,7 @@ const translation = {
ptBR: '葡萄牙语',
ruRU: '俄语',
ukUA: '乌克兰语',
viVN: '越南语',
},
},
unit: {

View File

@ -30,6 +30,7 @@ const translation = {
tos: '使用协议',
pp: '隐私政策',
tosDesc: '使用即代表你并同意我们的',
goToInit: '如果您还没有初始化账户,请前往初始化页面',
donthave: '还没有邀请码?',
invalidInvitationCode: '无效的邀请码',
accountAlreadyInited: '账户已经初始化',

View File

@ -44,6 +44,7 @@ const translation = {
voice: {
language: {
zhHans: '中文',
zhHant: '繁體中文',
enUS: '英語',
deDE: '德語',
frFR: '法語',
@ -56,6 +57,7 @@ const translation = {
ptBR: '葡萄牙語',
ruRU: '俄語',
ukUA: '烏克蘭語',
viVN: '越南語',
},
},
unit: {

View File

@ -30,6 +30,7 @@ const translation = {
tos: '使用協議',
pp: '隱私政策',
tosDesc: '使用即代表你並同意我們的',
goToInit: '如果您還沒有初始化賬戶,請前往初始化頁面',
donthave: '還沒有邀請碼?',
invalidInvitationCode: '無效的邀請碼',
accountAlreadyInited: '賬戶已經初始化',