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 {
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 { RiApps2AddLine, RiInformation2Line } from '@remixicon/react'
import s from './style.module.css'
import classNames from '@/utils/classnames'
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 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, 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 { 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 }
}
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>) => {
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)">
@ -117,65 +68,80 @@ const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
type IExtraInfoProps = {
isMobile: boolean
relatedApps?: RelatedAppResponse
expand: boolean
}
const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
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 className='w-full flex flex-col items-center'>
<Divider className='mt-5' />
{(relatedApps?.data && relatedApps?.data?.length > 0) && (
return <div>
{hasRelatedApps && (
<>
{!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')}>
{relatedApps?.total || '--'}
{relatedAppsTotal || '--'}
<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' />
{!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'
>
<BookOpenIcon className='mr-1' />
{t('common.datasetMenus.viewDoc')}
</a>
</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/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 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>
</FloatPopoverContainer>
</Tooltip>
)}
</div>
}
@ -246,7 +212,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
desc={datasetRes?.description || '--'}
isExternal={datasetRes?.provider === 'external'}
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'}
/>}
<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 {
@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 AppBasic from './basic'
import AppInfo from './app-info'
import DatasetInfo from './dataset-info'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import {
AlignLeft01,
AlignRight01,
} from '@/app/components/base/icons/src/vender/line/layout'
import { useStore as useAppStore } from '@/app/components/app/store'
import cn from '@/utils/classnames'
export type IAppDetailNavProps = {
iconType?: 'app' | 'dataset' | 'notion'
@ -63,7 +65,16 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
{iconType === 'app' && (
<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
mode={appSidebarExpand}
iconType={iconType}
@ -75,9 +86,9 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
/>
)}
</div>
{!expand && (
<div className='mt-1 mx-auto w-6 h-[1px] bg-divider-subtle' />
)}
<div className='px-4'>
<div className={cn('mt-1 mx-auto h-[1px] bg-divider-subtle', !expand && 'w-6')} />
</div>
<nav
className={`
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} />
)
})}
{extraInfo && extraInfo(appSidebarExpand)}
</nav>
{
!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
children?: React.ReactNode
popupClassName?: string
noDecoration?: boolean
offset?: OffsetOptions
needsDelay?: boolean
asChild?: boolean
@ -27,6 +28,7 @@ const Tooltip: FC<TooltipProps> = ({
popupContent,
children,
popupClassName,
noDecoration,
offset,
asChild = true,
needsDelay = false,
@ -96,7 +98,7 @@ const Tooltip: FC<TooltipProps> = ({
>
{popupContent && (<div
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,
)}
onMouseEnter={() => triggerMethod === 'hover' && setHoverPopup()}

View File

@ -64,7 +64,7 @@ const RetrievalMethodConfig: FC<Props> = ({
isActive={
value.search_method === RETRIEVE_METHOD.semantic
}
onClick={() => onChange({
onSwitched={() => onChange({
...value,
search_method: RETRIEVE_METHOD.semantic,
})}
@ -85,7 +85,7 @@ const RetrievalMethodConfig: FC<Props> = ({
isActive={
value.search_method === RETRIEVE_METHOD.fullText
}
onClick={() => onChange({
onSwitched={() => onChange({
...value,
search_method: RETRIEVE_METHOD.fullText,
})}
@ -104,7 +104,7 @@ const RetrievalMethodConfig: FC<Props> = ({
title={
<div className='flex items-center space-x-1'>
<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>
}
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 { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
import { updateDatasetSetting } from '@/service/datasets'
import type { DataSetListResponse } from '@/models/datasets'
import { type DataSetListResponse } from '@/models/datasets'
import DatasetDetailContext from '@/context/dataset-detail'
import { type RetrievalConfig } from '@/types/app'
import { useAppContext } from '@/context/app-context'
@ -234,6 +234,7 @@ const Form = () => {
disable={!currentDataset?.embedding_available}
value={indexMethod}
onChange={v => setIndexMethod(v)}
docForm={currentDataset.doc_form}
/>
</div>
</div>

View File

@ -1,8 +1,10 @@
'use client'
import { useTranslation } from 'react-i18next'
import { IndexingType } from '../../create/step-two'
import s from './index.module.css'
import classNames from '@/utils/classnames'
import type { DataSet } from '@/models/datasets'
import { ChuckingMode } from '@/models/datasets'
const itemClass = `
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
disable?: boolean
itemClassName?: string
docForm?: ChuckingMode
}
const IndexMethodRadio = ({
@ -22,6 +25,7 @@ const IndexMethodRadio = ({
onChange,
disable,
itemClassName,
docForm,
}: IIndexMethodRadioProps) => {
const { t } = useTranslation()
const options = [
@ -42,29 +46,35 @@ const IndexMethodRadio = ({
return (
<div className={classNames(s.wrapper, 'flex justify-between w-full flex-wrap gap-y-2')}>
{
options.map(option => (
<div
key={option.key}
className={classNames(
itemClass,
itemClassName,
s.item,
option.key === value && s['item-active'],
disable && s.disable,
)}
onClick={() => {
if (!disable)
onChange(option.key as DataSet['indexing_technique'])
}}
>
<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)} />
options.map((option) => {
const isParentChild = docForm === ChuckingMode.parentChild
return (
<div
key={option.key}
className={classNames(
itemClass,
itemClassName,
s.item,
option.key === value && s['item-active'],
disable && s.disable,
isParentChild && option.key === IndexingType.ECONOMICAL && s.disable,
)}
onClick={() => {
if (isParentChild && option.key === IndexingType.ECONOMICAL)
return
if (!disable)
onChange(option.key as DataSet['indexing_technique'])
}}
>
<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 className='pl-9 text-xs text-gray-500 leading-[18px]'>{option.desc}</div>
</div>
))
)
})
}
</div>
)

View File

@ -474,9 +474,10 @@ const translation = {
documents: 'Documents',
hitTesting: 'Retrieval Testing',
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',
relatedApp: 'linked apps',
noRelatedApp: 'No linked apps',
},
voiceInput: {
speaking: 'Speak now...',

View File

@ -16,6 +16,7 @@ const translation = {
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.',
externalAPIPanelDocumentation: 'Learn how to create an External Knowledge API',
localDocs: 'Local Docs',
documentCount: ' docs',
wordCount: ' k words',
appCount: ' linked apps',

View File

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

View File

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