Merge branch 'feat/parent-child-retrieval' of https://github.com/langgenius/dify into feat/parent-child-retrieval

This commit is contained in:
twwu 2024-12-10 18:01:43 +08:00
commit 8baaf7c84e
13 changed files with 230 additions and 137 deletions

View File

@ -7,85 +7,36 @@ import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { import {
Cog8ToothIcon, Cog8ToothIcon,
// CommandLineIcon,
Squares2X2Icon,
// eslint-disable-next-line sort-imports
PuzzlePieceIcon,
DocumentTextIcon, DocumentTextIcon,
PaperClipIcon, PaperClipIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/24/outline' } from '@heroicons/react/24/outline'
import { import {
Cog8ToothIcon as Cog8ToothSolidIcon, Cog8ToothIcon as Cog8ToothSolidIcon,
// CommandLineIcon as CommandLineSolidIcon, // CommandLineIcon as CommandLineSolidIcon,
DocumentTextIcon as DocumentTextSolidIcon, DocumentTextIcon as DocumentTextSolidIcon,
} from '@heroicons/react/24/solid' } from '@heroicons/react/24/solid'
import Link from 'next/link' import { RiApps2AddLine, RiInformation2Line } from '@remixicon/react'
import s from './style.module.css' import s from './style.module.css'
import classNames from '@/utils/classnames' import classNames from '@/utils/classnames'
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets' import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
import type { RelatedApp, RelatedAppResponse } from '@/models/datasets' import type { RelatedAppResponse } from '@/models/datasets'
import AppSideBar from '@/app/components/app-sidebar' 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 Loading from '@/app/components/base/loading'
import FloatPopoverContainer from '@/app/components/base/float-popover-container'
import DatasetDetailContext from '@/context/dataset-detail' import DatasetDetailContext from '@/context/dataset-detail'
import { DataSourceType } from '@/models/datasets' import { DataSourceType } from '@/models/datasets'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { LanguagesSupported } from '@/i18n/language' import { LanguagesSupported } from '@/i18n/language'
import { useStore } from '@/app/components/app/store' import { useStore } from '@/app/components/app/store'
import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication'
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
import { getLocaleOnClient } from '@/i18n' import { getLocaleOnClient } from '@/i18n'
import { useAppContext } from '@/context/app-context' 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 = { export type IAppDetailLayoutProps = {
children: React.ReactNode children: React.ReactNode
params: { datasetId: string } 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' iconType={detail.icon_type} icon={detail.icon} background={detail.icon_background} imageUrl={detail.icon_url} />
{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' && (
<CuteRobot 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>) => { 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 ?? ''}> 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)"> <g clipPath="url(#clip0_4610_6951)">
@ -117,65 +68,80 @@ const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
type IExtraInfoProps = { type IExtraInfoProps = {
isMobile: boolean isMobile: boolean
relatedApps?: RelatedAppResponse relatedApps?: RelatedAppResponse
expand: boolean
} }
const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => { const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
const locale = getLocaleOnClient() const locale = getLocaleOnClient()
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile) const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
const { t } = useTranslation() const { t } = useTranslation()
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
const relatedAppsTotal = relatedApps?.data?.length || 0
useEffect(() => { useEffect(() => {
setShowTips(!isMobile) setShowTips(!isMobile)
}, [isMobile, setShowTips]) }, [isMobile, setShowTips])
return <div className='w-full flex flex-col items-center'> return <div>
<Divider className='mt-5' /> {hasRelatedApps && (
{(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 && (
<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')}> {isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
{relatedApps?.total || '--'} {relatedAppsTotal || '--'}
<PaperClipIcon className='h-4 w-4 text-gray-700' /> <PaperClipIcon className='h-4 w-4 text-gray-700' />
</div>} </div>}
{relatedApps?.data?.map((item, index) => (<LikedItem key={index} isMobile={isMobile} detail={item} />))}
</> </>
)} )}
{!relatedApps?.data?.length && ( {!hasRelatedApps && !expand && (
<FloatPopoverContainer <Tooltip
placement='bottom-start' position='right'
open={isShowTips} noDecoration
toggle={toggleTips} needsDelay
isMobile={isMobile} popupContent={
triggerElement={ <div className='p-4 w-[240px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border rounded-xl'>
<div className={classNames('h-7 w-7 inline-flex justify-center items-center rounded-lg bg-transparent', isShowTips && '!bg-gray-50')}> <div className='inline-flex p-2 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle'>
<QuestionMarkCircleIcon className='h-4 w-4 flex-shrink-0 text-gray-500' /> <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'
>
<BookOpenIcon className='mr-1' />
{t('common.datasetMenus.viewDoc')}
</a>
</div> </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='inline-flex items-center system-xs-medium-uppercase text-text-secondary space-x-1 cursor-pointer'>
<div className='flex items-center justify-start gap-2'> <span>{t('common.datasetMenus.noRelatedApp')}</span>
<div className={s.emptyIconDiv}> <RiInformation2Line className='w-4 h-4' />
<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/knowledge-base/integrate_knowledge_within_application'
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
}
target='_blank' rel='noopener noreferrer'
>
<BookOpenIcon className='mr-1' />
{t('common.datasetMenus.viewDoc')}
</a>
</div> </div>
</FloatPopoverContainer> </Tooltip>
)} )}
</div> </div>
} }
@ -246,7 +212,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
desc={datasetRes?.description || '--'} desc={datasetRes?.description || '--'}
isExternal={datasetRes?.provider === 'external'} isExternal={datasetRes?.provider === 'external'}
navigation={navigation} navigation={navigation}
extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} /> : undefined} extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} expand={mode === 'collapse'} /> : undefined}
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
/>} />}
<DatasetDetailContext.Provider value={{ <DatasetDetailContext.Provider value={{

View File

@ -1,12 +1,3 @@
.itemWrapper {
@apply flex items-center w-full h-10 rounded-lg hover:bg-gray-50 cursor-pointer;
}
.appInfo {
@apply truncate text-gray-700 text-sm font-normal;
}
.iconWrapper {
@apply relative w-6 h-6 rounded-lg;
}
.statusPoint { .statusPoint {
@apply flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded; @apply flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded;
} }

View File

@ -0,0 +1,45 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '../base/app-icon'
const DatasetSvg = <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M0.833497 5.13481C0.833483 4.69553 0.83347 4.31654 0.858973 4.0044C0.88589 3.67495 0.94532 3.34727 1.10598 3.03195C1.34567 2.56155 1.72812 2.17909 2.19852 1.93941C2.51384 1.77875 2.84152 1.71932 3.17097 1.6924C3.48312 1.6669 3.86209 1.66691 4.30137 1.66693L7.62238 1.66684C8.11701 1.66618 8.55199 1.66561 8.95195 1.80356C9.30227 1.92439 9.62134 2.12159 9.88607 2.38088C10.1883 2.67692 10.3823 3.06624 10.603 3.50894L11.3484 5.00008H14.3679C15.0387 5.00007 15.5924 5.00006 16.0434 5.03691C16.5118 5.07518 16.9424 5.15732 17.3468 5.36339C17.974 5.68297 18.4839 6.19291 18.8035 6.82011C19.0096 7.22456 19.0917 7.65515 19.13 8.12356C19.1668 8.57455 19.1668 9.12818 19.1668 9.79898V13.5345C19.1668 14.2053 19.1668 14.7589 19.13 15.2099C19.0917 15.6784 19.0096 16.1089 18.8035 16.5134C18.4839 17.1406 17.974 17.6505 17.3468 17.9701C16.9424 18.1762 16.5118 18.2583 16.0434 18.2966C15.5924 18.3334 15.0387 18.3334 14.3679 18.3334H5.63243C4.96163 18.3334 4.40797 18.3334 3.95698 18.2966C3.48856 18.2583 3.05798 18.1762 2.65353 17.9701C2.02632 17.6505 1.51639 17.1406 1.19681 16.5134C0.990734 16.1089 0.908597 15.6784 0.870326 15.2099C0.833478 14.7589 0.833487 14.2053 0.833497 13.5345V5.13481ZM7.51874 3.33359C8.17742 3.33359 8.30798 3.34447 8.4085 3.37914C8.52527 3.41942 8.63163 3.48515 8.71987 3.57158C8.79584 3.64598 8.86396 3.7579 9.15852 4.34704L9.48505 5.00008L2.50023 5.00008C2.50059 4.61259 2.50314 4.34771 2.5201 4.14012C2.5386 3.91374 2.57 3.82981 2.59099 3.7886C2.67089 3.6318 2.79837 3.50432 2.95517 3.42442C2.99638 3.40343 3.08031 3.37203 3.30669 3.35353C3.54281 3.33424 3.85304 3.33359 4.3335 3.33359H7.51874Z" fill="#444CE7" />
</svg>
type Props = {
isExternal?: boolean
name: string
description: string
expand: boolean
extraInfo?: React.ReactNode
}
const DatasetInfo: FC<Props> = ({
name,
description,
isExternal,
expand,
extraInfo,
}) => {
const { t } = useTranslation()
return (
<div className='pl-1 pt-1'>
<div className='flex-shrink-0 mr-3'>
<AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' />
</div>
{expand && (
<div className='mt-2'>
<div className='system-md-semibold text-text-secondary'>
{name}
</div>
<div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? t('dataset.externalTag') : t('dataset.localDocs')}</div>
<div className='my-3 system-xs-regular text-text-tertiary'>{description}</div>
</div>
)}
{extraInfo}
</div>
)
}
export default React.memo(DatasetInfo)

View File

@ -4,12 +4,14 @@ import NavLink from './navLink'
import type { NavIcon } from './navLink' import type { NavIcon } from './navLink'
import AppBasic from './basic' import AppBasic from './basic'
import AppInfo from './app-info' import AppInfo from './app-info'
import DatasetInfo from './dataset-info'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { import {
AlignLeft01, AlignLeft01,
AlignRight01, AlignRight01,
} from '@/app/components/base/icons/src/vender/line/layout' } from '@/app/components/base/icons/src/vender/line/layout'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import cn from '@/utils/classnames'
export type IAppDetailNavProps = { export type IAppDetailNavProps = {
iconType?: 'app' | 'dataset' | 'notion' iconType?: 'app' | 'dataset' | 'notion'
@ -63,7 +65,16 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
{iconType === 'app' && ( {iconType === 'app' && (
<AppInfo expand={expand} /> <AppInfo expand={expand} />
)} )}
{iconType !== 'app' && ( {iconType === 'dataset' && (
<DatasetInfo
name={title}
description={desc}
isExternal={isExternal}
expand={expand}
extraInfo={extraInfo && extraInfo(appSidebarExpand)}
/>
)}
{!['app', 'dataset'].includes(iconType) && (
<AppBasic <AppBasic
mode={appSidebarExpand} mode={appSidebarExpand}
iconType={iconType} iconType={iconType}
@ -75,9 +86,9 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
/> />
)} )}
</div> </div>
{!expand && ( <div className='px-4'>
<div className='mt-1 mx-auto w-6 h-[1px] bg-divider-subtle' /> <div className={cn('mt-1 mx-auto h-[1px] bg-divider-subtle', !expand && 'w-6')} />
)} </div>
<nav <nav
className={` className={`
grow space-y-1 grow space-y-1
@ -89,7 +100,6 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
<NavLink key={index} mode={appSidebarExpand} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} /> <NavLink key={index} mode={appSidebarExpand} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
) )
})} })}
{extraInfo && extraInfo(appSidebarExpand)}
</nav> </nav>
{ {
!isMobile && ( !isMobile && (

View File

@ -0,0 +1,62 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import { RiArrowRightUpLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import AppIcon from '@/app/components/base/app-icon'
import type { RelatedApp } from '@/models/datasets'
type ILikedItemProps = {
appStatus?: boolean
detail: RelatedApp
isMobile: boolean
}
const appTypeMap = {
'chat': 'Chatbot',
'completion': 'Completion',
'agent-chat': 'Agent',
'advanced-chat': 'Chatflow',
'workflow': 'Workflow',
}
const LikedItem = ({
detail,
isMobile,
}: ILikedItemProps) => {
return (
<Link className={cn('group/link-item flex items-center justify-between w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
<div className='flex items-center'>
<div className={cn('relative w-6 h-6 rounded-md')}>
<AppIcon size='tiny' iconType={detail.icon_type} icon={detail.icon} background={detail.icon_background} imageUrl={detail.icon_url} />
</div>
{!isMobile && <div className={cn(' ml-2 truncate system-sm-medium text-text-primary')}>{detail?.name || '--'}</div>}
</div>
<div className='group-hover/link-item:hidden shrink-0 system-2xs-medium-uppercase text-text-tertiary'>{appTypeMap[detail.mode]}</div>
<RiArrowRightUpLine className='hidden group-hover/link-item:block w-4 h-4 text-text-tertiary' />
</Link>
)
}
type Props = {
relatedApps: RelatedApp[]
isMobile: boolean
}
const LinkedAppsPanel: FC<Props> = ({
relatedApps,
isMobile,
}) => {
const { t } = useTranslation()
return (
<div className='p-1 w-[320px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg rounded-xl backdrop-blur-[5px]'>
<div className='mt-1 mb-0.5 pl-2 system-xs-medium-uppercase text-text-tertiary'>{relatedApps.length || '--'} {t('common.datasetMenus.relatedApp')}</div>
{relatedApps.map((item, index) => (
<LikedItem key={index} detail={item} isMobile={isMobile} />
))}
</div>
)
}
export default React.memo(LinkedAppsPanel)

View File

@ -14,6 +14,7 @@ export type TooltipProps = {
popupContent?: React.ReactNode popupContent?: React.ReactNode
children?: React.ReactNode children?: React.ReactNode
popupClassName?: string popupClassName?: string
noDecoration?: boolean
offset?: OffsetOptions offset?: OffsetOptions
needsDelay?: boolean needsDelay?: boolean
asChild?: boolean asChild?: boolean
@ -27,6 +28,7 @@ const Tooltip: FC<TooltipProps> = ({
popupContent, popupContent,
children, children,
popupClassName, popupClassName,
noDecoration,
offset, offset,
asChild = true, asChild = true,
needsDelay = false, needsDelay = false,
@ -96,7 +98,7 @@ const Tooltip: FC<TooltipProps> = ({
> >
{popupContent && (<div {popupContent && (<div
className={cn( className={cn(
'relative px-3 py-2 text-xs font-normal text-text-secondary bg-components-tooltip-bg rounded-md shadow-lg break-words', !noDecoration && 'relative px-3 py-2 text-xs font-normal text-text-secondary bg-components-tooltip-bg rounded-md shadow-lg break-words',
popupClassName, popupClassName,
)} )}
onMouseEnter={() => triggerMethod === 'hover' && setHoverPopup()} onMouseEnter={() => triggerMethod === 'hover' && setHoverPopup()}

View File

@ -64,7 +64,7 @@ const RetrievalMethodConfig: FC<Props> = ({
isActive={ isActive={
value.search_method === RETRIEVE_METHOD.semantic value.search_method === RETRIEVE_METHOD.semantic
} }
onClick={() => onChange({ onSwitched={() => onChange({
...value, ...value,
search_method: RETRIEVE_METHOD.semantic, search_method: RETRIEVE_METHOD.semantic,
})} })}
@ -85,7 +85,7 @@ const RetrievalMethodConfig: FC<Props> = ({
isActive={ isActive={
value.search_method === RETRIEVE_METHOD.fullText value.search_method === RETRIEVE_METHOD.fullText
} }
onClick={() => onChange({ onSwitched={() => onChange({
...value, ...value,
search_method: RETRIEVE_METHOD.fullText, search_method: RETRIEVE_METHOD.fullText,
})} })}
@ -104,7 +104,7 @@ const RetrievalMethodConfig: FC<Props> = ({
title={ title={
<div className='flex items-center space-x-1'> <div className='flex items-center space-x-1'>
<div>{t('dataset.retrieval.hybrid_search.title')}</div> <div>{t('dataset.retrieval.hybrid_search.title')}</div>
<Badge text={t('dataset.retrieval.hybrid_search.recommend')} className='border-text-accent-secondary text-text-accent-secondary ml-2' uppercase /> <Badge text={t('dataset.retrieval.hybrid_search.recommend')!} className='border-text-accent-secondary text-text-accent-secondary ml-2' uppercase />
</div> </div>
} }
description={t('dataset.retrieval.hybrid_search.description')} isActive={ description={t('dataset.retrieval.hybrid_search.description')} isActive={

View File

@ -17,7 +17,7 @@ import Textarea from '@/app/components/base/textarea'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development' import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
import { updateDatasetSetting } from '@/service/datasets' import { updateDatasetSetting } from '@/service/datasets'
import type { DataSetListResponse } from '@/models/datasets' import { type DataSetListResponse } from '@/models/datasets'
import DatasetDetailContext from '@/context/dataset-detail' import DatasetDetailContext from '@/context/dataset-detail'
import { type RetrievalConfig } from '@/types/app' import { type RetrievalConfig } from '@/types/app'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
@ -234,6 +234,7 @@ const Form = () => {
disable={!currentDataset?.embedding_available} disable={!currentDataset?.embedding_available}
value={indexMethod} value={indexMethod}
onChange={v => setIndexMethod(v)} onChange={v => setIndexMethod(v)}
docForm={currentDataset.doc_form}
/> />
</div> </div>
</div> </div>

View File

@ -1,8 +1,10 @@
'use client' 'use client'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { IndexingType } from '../../create/step-two'
import s from './index.module.css' import s from './index.module.css'
import classNames from '@/utils/classnames' import classNames from '@/utils/classnames'
import type { DataSet } from '@/models/datasets' import type { DataSet } from '@/models/datasets'
import { ChuckingMode } from '@/models/datasets'
const itemClass = ` const itemClass = `
w-full sm:w-[234px] p-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer w-full sm:w-[234px] p-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer
@ -15,6 +17,7 @@ type IIndexMethodRadioProps = {
onChange: (v?: DataSet['indexing_technique']) => void onChange: (v?: DataSet['indexing_technique']) => void
disable?: boolean disable?: boolean
itemClassName?: string itemClassName?: string
docForm?: ChuckingMode
} }
const IndexMethodRadio = ({ const IndexMethodRadio = ({
@ -22,6 +25,7 @@ const IndexMethodRadio = ({
onChange, onChange,
disable, disable,
itemClassName, itemClassName,
docForm,
}: IIndexMethodRadioProps) => { }: IIndexMethodRadioProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const options = [ const options = [
@ -42,29 +46,35 @@ const IndexMethodRadio = ({
return ( return (
<div className={classNames(s.wrapper, 'flex justify-between w-full flex-wrap gap-y-2')}> <div className={classNames(s.wrapper, 'flex justify-between w-full flex-wrap gap-y-2')}>
{ {
options.map(option => ( options.map((option) => {
<div const isParentChild = docForm === ChuckingMode.parentChild
key={option.key} return (
className={classNames( <div
itemClass, key={option.key}
itemClassName, className={classNames(
s.item, itemClass,
option.key === value && s['item-active'], itemClassName,
disable && s.disable, s.item,
)} option.key === value && s['item-active'],
onClick={() => { disable && s.disable,
if (!disable) isParentChild && option.key === IndexingType.ECONOMICAL && s.disable,
onChange(option.key as DataSet['indexing_technique']) )}
}} onClick={() => {
> if (isParentChild && option.key === IndexingType.ECONOMICAL)
<div className='flex items-center mb-1'> return
<div className={classNames(s.icon, s[`${option.icon}-icon`])} /> if (!disable)
<div className='grow text-sm text-gray-900'>{option.text}</div> onChange(option.key as DataSet['indexing_technique'])
<div className={classNames(radioClass, s.radio)} /> }}
>
<div className='flex items-center mb-1'>
<div className={classNames(s.icon, s[`${option.icon}-icon`])} />
<div className='grow text-sm text-gray-900'>{option.text}</div>
<div className={classNames(radioClass, s.radio)} />
</div>
<div className='pl-9 text-xs text-gray-500 leading-[18px]'>{option.desc}</div>
</div> </div>
<div className='pl-9 text-xs text-gray-500 leading-[18px]'>{option.desc}</div> )
</div> })
))
} }
</div> </div>
) )

View File

@ -474,9 +474,10 @@ const translation = {
documents: 'Documents', documents: 'Documents',
hitTesting: 'Retrieval Testing', hitTesting: 'Retrieval Testing',
settings: 'Settings', settings: 'Settings',
emptyTip: 'The Knowledge has not been associated, please go to the application or plug-in to complete the association.', emptyTip: 'This Knowledge has not been integrated within any application. Please refer to the document for guidance.',
viewDoc: 'View documentation', viewDoc: 'View documentation',
relatedApp: 'linked apps', relatedApp: 'linked apps',
noRelatedApp: 'No linked apps',
}, },
voiceInput: { voiceInput: {
speaking: 'Speak now...', speaking: 'Speak now...',

View File

@ -16,6 +16,7 @@ const translation = {
learnHowToWriteGoodKnowledgeDescription: 'Learn how to write a good knowledge description', learnHowToWriteGoodKnowledgeDescription: 'Learn how to write a good knowledge description',
externalAPIPanelDescription: 'The external knowledge API is used to connect to a knowledge base outside of Dify and retrieve knowledge from that knowledge base.', externalAPIPanelDescription: 'The external knowledge API is used to connect to a knowledge base outside of Dify and retrieve knowledge from that knowledge base.',
externalAPIPanelDocumentation: 'Learn how to create an External Knowledge API', externalAPIPanelDocumentation: 'Learn how to create an External Knowledge API',
localDocs: 'Local Docs',
documentCount: ' docs', documentCount: ' docs',
wordCount: ' k words', wordCount: ' k words',
appCount: ' linked apps', appCount: ' linked apps',

View File

@ -474,9 +474,10 @@ const translation = {
documents: '文档', documents: '文档',
hitTesting: '召回测试', hitTesting: '召回测试',
settings: '设置', settings: '设置',
emptyTip: ' 知识库尚未关联,请前往应用程序或插件完成关联。', emptyTip: '此知识尚未集成到任何应用程序中。请参阅文档以获取指导。',
viewDoc: '查看文档', viewDoc: '查看文档',
relatedApp: '个关联应用', relatedApp: '个关联应用',
noRelatedApp: '无关联应用',
}, },
voiceInput: { voiceInput: {
speaking: '现在讲...', speaking: '现在讲...',

View File

@ -16,6 +16,7 @@ const translation = {
learnHowToWriteGoodKnowledgeDescription: '了解如何编写良好的知识库描述', learnHowToWriteGoodKnowledgeDescription: '了解如何编写良好的知识库描述',
externalAPIPanelDescription: '外部知识库 API 用于连接到 Dify 之外的知识库并从中检索知识。', externalAPIPanelDescription: '外部知识库 API 用于连接到 Dify 之外的知识库并从中检索知识。',
externalAPIPanelDocumentation: '了解如何创建外部知识库 API', externalAPIPanelDocumentation: '了解如何创建外部知识库 API',
localDocs: '本地文档',
documentCount: ' 文档', documentCount: ' 文档',
wordCount: ' 千字符', wordCount: ' 千字符',
appCount: ' 关联应用', appCount: ' 关联应用',
@ -119,8 +120,10 @@ const translation = {
change: '更改', change: '更改',
changeRetrievalMethod: '更改检索方法', changeRetrievalMethod: '更改检索方法',
}, },
docsFailedNotice: '文档无法被索引', docsFailedNotice: '文档索引失败',
retry: '重试', retry: '重试',
documentsDisabled: '{{num}} 个文档已禁用 - 未活动超过 30 天',
enable: '启用',
indexingTechnique: { indexingTechnique: {
high_quality: '高质量', high_quality: '高质量',
economy: '经济', economy: '经济',