瀏覽代碼

feat: fe mobile responsive next (#1609)

tags/0.3.33
Yuhao 1 年之前
父節點
當前提交
a9c1c7d239
沒有連結到貢獻者的電子郵件帳戶。
共有 89 個檔案被更改,包括 767 行新增484 行删除
  1. 3
    3
      web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx
  2. 1
    1
      web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx
  3. 1
    1
      web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx
  4. 3
    3
      web/app/(commonLayout)/apps/NewAppDialog.tsx
  5. 89
    45
      web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx
  6. 2
    4
      web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx
  7. 2
    2
      web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/style.module.css
  8. 4
    4
      web/app/(commonLayout)/datasets/ApiServer.tsx
  9. 9
    11
      web/app/(commonLayout)/datasets/Container.tsx
  10. 1
    1
      web/app/(commonLayout)/datasets/Doc.tsx
  11. 9
    8
      web/app/components/app-sidebar/basic.tsx
  12. 9
    5
      web/app/components/app-sidebar/index.tsx
  13. 3
    1
      web/app/components/app-sidebar/navLink.tsx
  14. 10
    5
      web/app/components/app/configuration/config-model/index.tsx
  15. 1
    1
      web/app/components/app/configuration/config-model/model-mode-type-label.tsx
  16. 1
    1
      web/app/components/app/configuration/config-model/model-name.tsx
  17. 1
    1
      web/app/components/app/configuration/config-model/param-item.tsx
  18. 2
    2
      web/app/components/app/configuration/config-var/index.tsx
  19. 1
    1
      web/app/components/app/configuration/config-vision/param-config.tsx
  20. 30
    5
      web/app/components/app/configuration/config/automatic/get-automatic-res.tsx
  21. 13
    9
      web/app/components/app/configuration/dataset-config/card-item/item.tsx
  22. 1
    1
      web/app/components/app/configuration/dataset-config/params-config/index.tsx
  23. 6
    13
      web/app/components/app/configuration/dataset-config/settings-modal/index.tsx
  24. 28
    6
      web/app/components/app/configuration/index.tsx
  25. 2
    2
      web/app/components/app/log/filter.tsx
  26. 19
    14
      web/app/components/app/log/list.tsx
  27. 4
    4
      web/app/components/app/overview/appCard.tsx
  28. 2
    2
      web/app/components/app/overview/customize/index.tsx
  29. 2
    2
      web/app/components/app/overview/embedded/index.tsx
  30. 8
    2
      web/app/components/base/drawer/index.tsx
  31. 37
    0
      web/app/components/base/float-popover-container/index.tsx
  32. 23
    0
      web/app/components/base/float-right-container/index.tsx
  33. 1
    1
      web/app/components/base/popover/style.module.css
  34. 1
    1
      web/app/components/base/portal-to-follow-elem/index.tsx
  35. 1
    1
      web/app/components/base/select/index.tsx
  36. 2
    2
      web/app/components/datasets/create/file-uploader/index.module.css
  37. 5
    3
      web/app/components/datasets/create/file-uploader/index.tsx
  38. 1
    1
      web/app/components/datasets/create/index.tsx
  39. 0
    3
      web/app/components/datasets/create/step-one/index.module.css
  40. 1
    1
      web/app/components/datasets/create/step-one/index.tsx
  41. 7
    3
      web/app/components/datasets/create/step-three/index.tsx
  42. 3
    3
      web/app/components/datasets/create/step-two/index.module.css
  43. 85
    62
      web/app/components/datasets/create/step-two/index.tsx
  44. 3
    2
      web/app/components/datasets/create/steps-nav-bar/index.module.css
  45. 33
    23
      web/app/components/datasets/create/steps-nav-bar/index.tsx
  46. 1
    1
      web/app/components/datasets/documents/detail/completed/index.tsx
  47. 3
    3
      web/app/components/datasets/documents/detail/completed/style.module.css
  48. 45
    35
      web/app/components/datasets/documents/detail/index.tsx
  49. 1
    1
      web/app/components/datasets/documents/detail/metadata/style.module.css
  50. 2
    2
      web/app/components/datasets/documents/index.tsx
  51. 3
    3
      web/app/components/datasets/documents/list.tsx
  52. 2
    2
      web/app/components/datasets/hit-testing/hit-detail.tsx
  53. 60
    44
      web/app/components/datasets/hit-testing/index.tsx
  54. 2
    2
      web/app/components/datasets/hit-testing/style.module.css
  55. 6
    4
      web/app/components/datasets/hit-testing/textarea.tsx
  56. 7
    7
      web/app/components/datasets/settings/form/index.tsx
  57. 2
    2
      web/app/components/datasets/settings/index-method-radio/index.tsx
  58. 2
    2
      web/app/components/datasets/settings/permissions-radio/index.tsx
  59. 5
    5
      web/app/components/develop/index.tsx
  60. 0
    4
      web/app/components/develop/secret-key/style.module.css
  61. 1
    1
      web/app/components/explore/installed-app/index.tsx
  62. 21
    16
      web/app/components/explore/sidebar/app-nav-item/index.tsx
  63. 15
    8
      web/app/components/explore/sidebar/index.tsx
  64. 3
    5
      web/app/components/explore/universal-chat/index.tsx
  65. 5
    5
      web/app/components/explore/universal-chat/init/index.tsx
  66. 0
    3
      web/app/components/explore/universal-chat/style.module.css
  67. 2
    9
      web/app/components/header/HeaderWrapper.tsx
  68. 11
    4
      web/app/components/header/account-dropdown/index.tsx
  69. 1
    1
      web/app/components/header/account-setting/api-based-extension-page/selector.tsx
  70. 1
    1
      web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx
  71. 10
    5
      web/app/components/header/account-setting/index.tsx
  72. 3
    3
      web/app/components/header/account-setting/members-page/index.tsx
  73. 1
    1
      web/app/components/header/account-setting/model-page/index.tsx
  74. 1
    1
      web/app/components/header/account-setting/model-page/model-item/index.tsx
  75. 1
    1
      web/app/components/header/account-setting/model-page/model-modal/Form.tsx
  76. 2
    2
      web/app/components/header/account-setting/model-page/model-modal/index.tsx
  77. 65
    18
      web/app/components/header/index.tsx
  78. 1
    1
      web/app/components/header/nav/index.tsx
  79. 2
    3
      web/app/components/share/chat/index.tsx
  80. 0
    3
      web/app/components/share/chat/style.module.css
  81. 1
    2
      web/app/components/share/chatbot/index.tsx
  82. 0
    3
      web/app/components/share/chatbot/style.module.css
  83. 2
    2
      web/app/components/share/header.tsx
  84. 1
    1
      web/app/components/share/text-generation/style.module.css
  85. 2
    2
      web/context/app-context.tsx
  86. 1
    0
      web/i18n/lang/app-debug.en.ts
  87. 1
    0
      web/i18n/lang/app-debug.zh.ts
  88. 1
    0
      web/i18n/lang/dataset-creation.en.ts
  89. 1
    0
      web/i18n/lang/dataset-creation.zh.ts

+ 3
- 3
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx 查看文件

const navigation = useMemo(() => { const navigation = useMemo(() => {
const navs = [ const navs = [
{ name: t('common.appMenus.overview'), href: `/app/${appId}/overview`, icon: ChartBarSquareIcon, selectedIcon: ChartBarSquareSolidIcon }, { name: t('common.appMenus.overview'), href: `/app/${appId}/overview`, icon: ChartBarSquareIcon, selectedIcon: ChartBarSquareSolidIcon },
isCurrentWorkspaceManager ? { name: t('common.appMenus.promptEng'), href: `/app/${appId}/configuration`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon } : false,
...(isCurrentWorkspaceManager ? [{ name: t('common.appMenus.promptEng'), href: `/app/${appId}/configuration`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon }] : []),
{ name: t('common.appMenus.apiAccess'), href: `/app/${appId}/develop`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon }, { name: t('common.appMenus.apiAccess'), href: `/app/${appId}/develop`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
{ name: t('common.appMenus.logAndAnn'), href: `/app/${appId}/logs`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon }, { name: t('common.appMenus.logAndAnn'), href: `/app/${appId}/logs`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
].filter(nav => !!nav)
]
return navs return navs
}, [appId, isCurrentWorkspaceManager, t]) }, [appId, isCurrentWorkspaceManager, t])


return ( return (
<div className={cn(s.app, 'flex', 'overflow-hidden')}> <div className={cn(s.app, 'flex', 'overflow-hidden')}>
<AppSideBar title={response.name} icon={response.icon} icon_background={response.icon_background} desc={appModeName} navigation={navigation} /> <AppSideBar title={response.name} icon={response.icon} icon_background={response.icon_background} desc={appModeName} navigation={navigation} />
<div className="bg-white grow">{children}</div>
<div className="bg-white grow overflow-hidden">{children}</div>
</div> </div>
) )
} }

+ 1
- 1
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx 查看文件

} }


return ( return (
<div className="min-w-max grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6">
<div className="grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6">
<AppCard <AppCard
appInfo={response} appInfo={response}
cardType="webapp" cardType="webapp"

+ 1
- 1
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx 查看文件

*/ */
const { t } = await translate(locale, 'app-overview') const { t } = await translate(locale, 'app-overview')
return ( return (
<div className="h-full px-16 py-6 overflow-scroll">
<div className="h-full px-4 sm:px-16 py-6 overflow-scroll">
<ApikeyInfoPanel /> <ApikeyInfoPanel />
<div className='flex flex-row items-center justify-between mb-4 text-xl text-gray-900'> <div className='flex flex-row items-center justify-between mb-4 text-xl text-gray-900'>
{t('overview.title')} {t('overview.title')}

+ 3
- 3
web/app/(commonLayout)/apps/NewAppDialog.tsx 查看文件

<input ref={nameInputRef} className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' placeholder={t('app.appNamePlaceholder') || ''}/> <input ref={nameInputRef} className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' placeholder={t('app.appNamePlaceholder') || ''}/>
</div> </div>


<div className='h-[247px] overflow-y-auto'>
<div className='overflow-y-auto'>
<div className={style.newItemCaption}> <div className={style.newItemCaption}>
<h3 className='inline'>{t('app.newApp.captionAppType')}</h3> <h3 className='inline'>{t('app.newApp.captionAppType')}</h3>
{isWithTemplate && ( {isWithTemplate && (
</div> </div>
{isWithTemplate {isWithTemplate
? ( ? (
<ul className='grid grid-cols-2 gap-4'>
<ul className='grid grid-cols-1 md:grid-cols-2 gap-4'>
{templates?.data?.map((template, index) => ( {templates?.data?.map((template, index) => (
<li <li
key={index} key={index}
) )
: ( : (
<> <>
<ul className='grid grid-cols-2 gap-4'>
<ul className='grid grid-cols-1 md:grid-cols-2 gap-4'>
<li <li
className={classNames(style.listItem, style.selectable, newAppMode === 'chat' && style.selected)} className={classNames(style.listItem, style.selectable, newAppMode === 'chat' && style.selected)}
onClick={() => setNewAppMode('chat')} onClick={() => setNewAppMode('chat')}

+ 89
- 45
web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx 查看文件

import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import useSWR from 'swr' import useSWR from 'swr'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
import { useBoolean } from 'ahooks'
import { import {
Cog8ToothIcon, Cog8ToothIcon,
// CommandLineIcon, // CommandLineIcon,
// eslint-disable-next-line sort-imports // eslint-disable-next-line sort-imports
PuzzlePieceIcon, PuzzlePieceIcon,
DocumentTextIcon, DocumentTextIcon,
PaperClipIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/24/outline' } from '@heroicons/react/24/outline'
import { import {
Cog8ToothIcon as Cog8ToothSolidIcon, Cog8ToothIcon as Cog8ToothSolidIcon,
import Link from 'next/link' import Link from 'next/link'
import s from './style.module.css' import s from './style.module.css'
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets' import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
import type { RelatedApp } from '@/models/datasets'
import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
import { getLocaleOnClient } from '@/i18n/client' import { getLocaleOnClient } from '@/i18n/client'
import AppSideBar from '@/app/components/app-sidebar' import AppSideBar from '@/app/components/app-sidebar'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import Indicator from '@/app/components/header/indicator' import Indicator from '@/app/components/header/indicator'
import AppIcon from '@/app/components/base/app-icon' 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'


export type IAppDetailLayoutProps = { export type IAppDetailLayoutProps = {
children: React.ReactNode children: React.ReactNode
params: { datasetId: string } params: { datasetId: string }
} }


const LikedItem: FC<{ type?: 'plugin' | 'app'; appStatus?: boolean; detail: RelatedApp }> = ({
type ILikedItemProps = {
type?: 'plugin' | 'app'
appStatus?: boolean
detail: RelatedApp
isMobile: boolean
}

const LikedItem = ({
type = 'app', type = 'app',
appStatus = true, appStatus = true,
detail, detail,
}) => {
isMobile,
}: ILikedItemProps) => {
return ( return (
<Link className={s.itemWrapper} href={`/app/${detail?.id}/overview`}>
<div className={s.iconWrapper}>
<Link className={classNames(s.itemWrapper, 'px-0 sm:px-3 justify-center sm:justify-start')} href={`/app/${detail?.id}/overview`}>
<div className={classNames(s.iconWrapper, 'mr-0 sm:mr-2')}>
<AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background}/> <AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background}/>
{type === 'app' && ( {type === 'app' && (
<div className={s.statusPoint}> <div className={s.statusPoint}>
</div> </div>
)} )}
</div> </div>
<div className={s.appInfo}>{detail?.name || '--'}</div>
{!isMobile && <div className={s.appInfo}>{detail?.name || '--'}</div>}
</Link> </Link>
) )
} }
</svg> </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={s.subTitle}>{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-[150px]')}>
<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={`https://docs.dify.ai/${locale === 'zh-Hans' ? 'v/zh-hans' : ''}/application/prompt-engineering`}
target='_blank'
>
<BookOpenIcon className='mr-1' />
{t('common.datasetMenus.viewDoc')}
</a>
</div>
</FloatPopoverContainer>
)}
</div>
}

const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => { const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const { const {
children, children,
const pathname = usePathname() const pathname = usePathname()
const hideSideBar = /documents\/create$/.test(pathname) const hideSideBar = /documents\/create$/.test(pathname)
const { t } = useTranslation() const { t } = useTranslation()

const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({ const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
url: 'fetchDatasetDetail', url: 'fetchDatasetDetail',
datasetId, datasetId,
document.title = `${datasetRes.name || 'Dataset'} - Dify` document.title = `${datasetRes.name || 'Dataset'} - Dify`
}, [datasetRes]) }, [datasetRes])


const ExtraInfo: FC = () => {
const locale = getLocaleOnClient()

return <div className='w-full'>
<Divider className='mt-5' />
{relatedApps?.data?.length
? (
<>
<div className={s.subTitle}>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>
{relatedApps?.data?.map((item, index) => (<LikedItem key={index} detail={item} />))}
</>
)
: (
<div className='mt-5 p-3'>
<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={`https://docs.dify.ai/${locale === 'zh-Hans' ? 'v/zh-hans' : ''}/application/prompt-engineering`}
target='_blank'
>
<BookOpenIcon className='mr-1' />
{t('common.datasetMenus.viewDoc')}
</a>
</div>
)}
</div>
}

if (!datasetRes && !error) if (!datasetRes && !error)
return <Loading /> return <Loading />


return ( return (
<div className='flex'>
<div className='flex overflow-hidden'>
{!hideSideBar && <AppSideBar {!hideSideBar && <AppSideBar
title={datasetRes?.name || '--'} title={datasetRes?.name || '--'}
icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'} icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
icon_background={datasetRes?.icon_background || '#F5F5F5'} icon_background={datasetRes?.icon_background || '#F5F5F5'}
desc={datasetRes?.description || '--'} desc={datasetRes?.description || '--'}
navigation={navigation} navigation={navigation}
extraInfo={<ExtraInfo />}
extraInfo={<ExtraInfo isMobile={isMobile} relatedApps={relatedApps} />}
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
/>} />}
<DatasetDetailContext.Provider value={{ <DatasetDetailContext.Provider value={{
dataset: datasetRes, dataset: datasetRes,
mutateDatasetRes: () => mutateDatasetRes(), mutateDatasetRes: () => mutateDatasetRes(),
}}> }}>
<div className="bg-white grow" style={{ minHeight: 'calc(100vh - 56px)' }}>{children}</div>
<div className="bg-white grow overflow-hidden">{children}</div>
</DatasetDetailContext.Provider> </DatasetDetailContext.Provider>
</div> </div>
) )

+ 2
- 4
web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx 查看文件

const { t } = await useTranslation(locale, 'dataset-settings') const { t } = await useTranslation(locale, 'dataset-settings')


return ( return (
<div className='bg-white h-full'>
<div className='bg-white h-full overflow-y-auto'>
<div className='px-6 py-3'> <div className='px-6 py-3'>
<div className='mb-1 text-lg font-semibold text-gray-900'>{t('title')}</div> <div className='mb-1 text-lg font-semibold text-gray-900'>{t('title')}</div>
<div className='text-sm text-gray-500'>{t('desc')}</div> <div className='text-sm text-gray-500'>{t('desc')}</div>
</div> </div>
<div>
<Form datasetId={datasetId} />
</div>
<Form datasetId={datasetId} />
</div> </div>
) )
} }

+ 2
- 2
web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/style.module.css 查看文件

.itemWrapper { .itemWrapper {
@apply flex items-center w-full h-10 px-3 rounded-lg hover:bg-gray-50 cursor-pointer;
@apply flex items-center w-full h-10 rounded-lg hover:bg-gray-50 cursor-pointer;
} }
.appInfo { .appInfo {
@apply truncate text-gray-700 text-sm font-normal; @apply truncate text-gray-700 text-sm font-normal;
} }
.iconWrapper { .iconWrapper {
@apply relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-md;
@apply relative w-6 h-6 bg-[#D5F5F6] rounded-md;
} }
.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;

+ 4
- 4
web/app/(commonLayout)/datasets/ApiServer.tsx 查看文件

const { t } = useTranslation() const { t } = useTranslation()


return ( return (
<div className='flex items-center'>
<div className='flex items-center mr-2 pl-1.5 pr-1 h-8 bg-white/80 border-[0.5px] border-white rounded-lg'>
<div className='mr-0.5 px-1.5 h-5 border border-gray-200 text-[11px] text-gray-500 rounded-md'>{t('appApi.apiServer')}</div>
<div className='px-1 w-[248px] text-[13px] font-medium text-gray-800'>{apiBaseUrl}</div>
<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='mx-1 w-[1px] h-[14px] bg-gray-200'></div>
<CopyFeedback <CopyFeedback
content={apiBaseUrl} content={apiBaseUrl}

+ 9
- 11
web/app/(commonLayout)/datasets/Container.tsx 查看文件



return ( return (
<div ref={containerRef} className='grow relative flex flex-col bg-gray-100 overflow-y-auto'> <div ref={containerRef} className='grow relative flex flex-col bg-gray-100 overflow-y-auto'>
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 h-14 bg-gray-100 z-10'>
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-gray-100 z-10 flex-wrap gap-y-2'>
<TabSlider <TabSlider
value={activeTab} value={activeTab}
onChange={newActiveTab => setActiveTab(newActiveTab)} onChange={newActiveTab => setActiveTab(newActiveTab)}
{activeTab === 'api' && data && <ApiServer apiBaseUrl={data.api_base_url || ''} />} {activeTab === 'api' && data && <ApiServer apiBaseUrl={data.api_base_url || ''} />}
</div> </div>


{activeTab === 'dataset'
? (
<>
<Datasets containerRef={containerRef} />
<DatasetFooter />
</>
)
: (
activeTab === 'api' && data && <Doc apiBaseUrl={data.api_base_url || ''} />
)}
{activeTab === 'dataset' && (
<>
<Datasets containerRef={containerRef} />
<DatasetFooter />
</>
)}

{activeTab === 'api' && data && <Doc apiBaseUrl={data.api_base_url || ''} />}
</div> </div>


) )

+ 1
- 1
web/app/(commonLayout)/datasets/Doc.tsx 查看文件

const { locale } = useContext(I18n) const { locale } = useContext(I18n)


return ( return (
<article className='mx-12 pt-16 bg-white rounded-t-xl prose prose-xl'>
<article className='mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl'>
{ {
locale === 'en' locale === 'en'
? <TemplateEn apiBaseUrl={apiBaseUrl} /> ? <TemplateEn apiBaseUrl={apiBaseUrl} />

+ 9
- 8
web/app/components/app-sidebar/basic.tsx 查看文件

hoverTip?: string hoverTip?: string
textStyle?: { main?: string; extra?: string } textStyle?: { main?: string; extra?: string }
isExtraInLine?: boolean isExtraInLine?: boolean
mode?: 'expand' | 'collapse'
} }


const ApiSvg = <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> const ApiSvg = <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
</svg> </svg>


const WebappSvg = <svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg"> const WebappSvg = <svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.375 5.45825L7.99998 8.99992M7.99998 8.99992L1.62498 5.45825M7.99998 8.99992L8 16.1249M14.75 12.0439V5.95603C14.75 5.69904 14.75 5.57055 14.7121 5.45595C14.6786 5.35457 14.6239 5.26151 14.5515 5.18299C14.4697 5.09424 14.3574 5.03184 14.1328 4.90704L8.58277 1.8237C8.37007 1.70553 8.26372 1.64645 8.15109 1.62329C8.05141 1.60278 7.9486 1.60278 7.84891 1.62329C7.73628 1.64645 7.62993 1.70553 7.41723 1.8237L1.86723 4.90704C1.64259 5.03184 1.53026 5.09424 1.44847 5.18299C1.37612 5.26151 1.32136 5.35457 1.28786 5.45595C1.25 5.57055 1.25 5.69904 1.25 5.95603V12.0439C1.25 12.3008 1.25 12.4293 1.28786 12.5439C1.32136 12.6453 1.37612 12.7384 1.44847 12.8169C1.53026 12.9056 1.64259 12.968 1.86723 13.0928L7.41723 16.1762C7.62993 16.2943 7.73628 16.3534 7.84891 16.3766C7.9486 16.3971 8.05141 16.3971 8.15109 16.3766C8.26372 16.3534 8.37007 16.2943 8.58277 16.1762L14.1328 13.0928C14.3574 12.968 14.4697 12.9056 14.5515 12.8169C14.6239 12.7384 14.6786 12.6453 14.7121 12.5439C14.75 12.4293 14.75 12.3008 14.75 12.0439Z" stroke="#155EEF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M14.375 5.45825L7.99998 8.99992M7.99998 8.99992L1.62498 5.45825M7.99998 8.99992L8 16.1249M14.75 12.0439V5.95603C14.75 5.69904 14.75 5.57055 14.7121 5.45595C14.6786 5.35457 14.6239 5.26151 14.5515 5.18299C14.4697 5.09424 14.3574 5.03184 14.1328 4.90704L8.58277 1.8237C8.37007 1.70553 8.26372 1.64645 8.15109 1.62329C8.05141 1.60278 7.9486 1.60278 7.84891 1.62329C7.73628 1.64645 7.62993 1.70553 7.41723 1.8237L1.86723 4.90704C1.64259 5.03184 1.53026 5.09424 1.44847 5.18299C1.37612 5.26151 1.32136 5.35457 1.28786 5.45595C1.25 5.57055 1.25 5.69904 1.25 5.95603V12.0439C1.25 12.3008 1.25 12.4293 1.28786 12.5439C1.32136 12.6453 1.37612 12.7384 1.44847 12.8169C1.53026 12.9056 1.64259 12.968 1.86723 13.0928L7.41723 16.1762C7.62993 16.2943 7.73628 16.3534 7.84891 16.3766C7.9486 16.3971 8.05141 16.3971 8.15109 16.3766C8.26372 16.3534 8.37007 16.2943 8.58277 16.1762L14.1328 13.0928C14.3574 12.968 14.4697 12.9056 14.5515 12.8169C14.6239 12.7384 14.6786 12.6453 14.7121 12.5439C14.75 12.4293 14.75 12.3008 14.75 12.0439Z" stroke="#155EEF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>


const NotionSvg = <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> const NotionSvg = <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6294_13848)"> <g clip-path="url(#clip0_6294_13848)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.287 21.9133L1.70748 18.6999C1.08685 17.9267 0.75 16.976 0.75 15.9974V4.36124C0.75 2.89548 1.92269 1.67923 3.43553 1.57594L15.3991 0.759137C16.2682 0.699797 17.1321 0.930818 17.8461 1.41353L22.0494 4.25543C22.8018 4.76414 23.25 5.59574 23.25 6.48319V19.7124C23.25 21.1468 22.0969 22.3345 20.6157 22.4256L7.3375 23.243C6.1555 23.3158 5.01299 22.8178 4.287 21.9133Z" fill="white"/>
<path d="M8.43607 10.1842V10.0318C8.43607 9.64564 8.74535 9.32537 9.14397 9.29876L12.0475 9.10491L16.0628 15.0178V9.82823L15.0293 9.69046V9.6181C15.0293 9.22739 15.3456 8.90501 15.7493 8.88433L18.3912 8.74899V9.12918C18.3912 9.30765 18.2585 9.46031 18.0766 9.49108L17.4408 9.59861V18.0029L16.6429 18.2773C15.9764 18.5065 15.2343 18.2611 14.8527 17.6853L10.9545 11.803V17.4173L12.1544 17.647L12.1377 17.7583C12.0853 18.1069 11.7843 18.3705 11.4202 18.3867L8.43607 18.5195C8.39662 18.1447 8.67758 17.8093 9.06518 17.7686L9.45771 17.7273V10.2416L8.43607 10.1842Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5062 2.22521L3.5426 3.04201C2.82599 3.09094 2.27051 3.66706 2.27051 4.36136V15.9975C2.27051 16.6499 2.49507 17.2837 2.90883 17.7992L5.48835 21.0126C5.90541 21.5322 6.56174 21.8183 7.24076 21.7765L20.519 20.9591C21.1995 20.9172 21.7293 20.3716 21.7293 19.7125V6.48332C21.7293 6.07557 21.5234 5.69348 21.1777 5.45975L16.9743 2.61784C16.546 2.32822 16.0277 2.1896 15.5062 2.22521ZM4.13585 4.54287C3.96946 4.41968 4.04865 4.16303 4.25768 4.14804L15.5866 3.33545C15.9476 3.30956 16.3063 3.40896 16.5982 3.61578L18.8713 5.22622C18.9576 5.28736 18.9171 5.41935 18.8102 5.42516L6.8129 6.07764C6.44983 6.09739 6.09144 5.99073 5.80276 5.77699L4.13585 4.54287ZM6.25018 8.12315C6.25018 7.7334 6.56506 7.41145 6.9677 7.38952L19.6523 6.69871C20.0447 6.67734 20.375 6.97912 20.375 7.35898V18.8141C20.375 19.2031 20.0613 19.5247 19.6594 19.5476L7.05516 20.2648C6.61845 20.2896 6.25018 19.954 6.25018 19.5312V8.12315Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.287 21.9133L1.70748 18.6999C1.08685 17.9267 0.75 16.976 0.75 15.9974V4.36124C0.75 2.89548 1.92269 1.67923 3.43553 1.57594L15.3991 0.759137C16.2682 0.699797 17.1321 0.930818 17.8461 1.41353L22.0494 4.25543C22.8018 4.76414 23.25 5.59574 23.25 6.48319V19.7124C23.25 21.1468 22.0969 22.3345 20.6157 22.4256L7.3375 23.243C6.1555 23.3158 5.01299 22.8178 4.287 21.9133Z" fill="white" />
<path d="M8.43607 10.1842V10.0318C8.43607 9.64564 8.74535 9.32537 9.14397 9.29876L12.0475 9.10491L16.0628 15.0178V9.82823L15.0293 9.69046V9.6181C15.0293 9.22739 15.3456 8.90501 15.7493 8.88433L18.3912 8.74899V9.12918C18.3912 9.30765 18.2585 9.46031 18.0766 9.49108L17.4408 9.59861V18.0029L16.6429 18.2773C15.9764 18.5065 15.2343 18.2611 14.8527 17.6853L10.9545 11.803V17.4173L12.1544 17.647L12.1377 17.7583C12.0853 18.1069 11.7843 18.3705 11.4202 18.3867L8.43607 18.5195C8.39662 18.1447 8.67758 17.8093 9.06518 17.7686L9.45771 17.7273V10.2416L8.43607 10.1842Z" fill="black" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5062 2.22521L3.5426 3.04201C2.82599 3.09094 2.27051 3.66706 2.27051 4.36136V15.9975C2.27051 16.6499 2.49507 17.2837 2.90883 17.7992L5.48835 21.0126C5.90541 21.5322 6.56174 21.8183 7.24076 21.7765L20.519 20.9591C21.1995 20.9172 21.7293 20.3716 21.7293 19.7125V6.48332C21.7293 6.07557 21.5234 5.69348 21.1777 5.45975L16.9743 2.61784C16.546 2.32822 16.0277 2.1896 15.5062 2.22521ZM4.13585 4.54287C3.96946 4.41968 4.04865 4.16303 4.25768 4.14804L15.5866 3.33545C15.9476 3.30956 16.3063 3.40896 16.5982 3.61578L18.8713 5.22622C18.9576 5.28736 18.9171 5.41935 18.8102 5.42516L6.8129 6.07764C6.44983 6.09739 6.09144 5.99073 5.80276 5.77699L4.13585 4.54287ZM6.25018 8.12315C6.25018 7.7334 6.56506 7.41145 6.9677 7.38952L19.6523 6.69871C20.0447 6.67734 20.375 6.97912 20.375 7.35898V18.8141C20.375 19.2031 20.0613 19.5247 19.6594 19.5476L7.05516 20.2648C6.61845 20.2896 6.25018 19.954 6.25018 19.5312V8.12315Z" fill="black" />
</g> </g>
<defs> <defs>
<clipPath id="clip0_6294_13848"> <clipPath id="clip0_6294_13848">
<rect width="24" height="24" fill="white"/>
<rect width="24" height="24" fill="white" />
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
notion: <AppIcon innerIcon={NotionSvg} className='!border-[0.5px] !border-indigo-100 !bg-white' />, notion: <AppIcon innerIcon={NotionSvg} className='!border-[0.5px] !border-indigo-100 !bg-white' />,
} }


export default function AppBasic({ icon, icon_background, name, type, hoverTip, textStyle, iconType = 'app', isExtraInLine }: IAppBasicProps) {
export default function AppBasic({ icon, icon_background, name, type, hoverTip, textStyle, mode = 'expand', iconType = 'app', isExtraInLine }: IAppBasicProps) {
return ( return (
<div className="flex items-start"> <div className="flex items-start">
{icon && icon_background && iconType === 'app' && ( {icon && icon_background && iconType === 'app' && (
</div> </div>


} }
<div className="group">
{mode === 'expand' && <div className="group">
<div className={`flex flex-row items-center text-sm font-semibold text-gray-700 group-hover:text-gray-900 break-all ${textStyle?.main ?? ''}`}> <div className={`flex flex-row items-center text-sm font-semibold text-gray-700 group-hover:text-gray-900 break-all ${textStyle?.main ?? ''}`}>
{name} {name}
{hoverTip {hoverTip
</Tooltip>} </Tooltip>}
</div> </div>
<div className={`text-xs font-normal text-gray-500 group-hover:text-gray-700 break-all ${textStyle?.extra ?? ''}`}>{type}</div> <div className={`text-xs font-normal text-gray-500 group-hover:text-gray-700 break-all ${textStyle?.extra ?? ''}`}>{type}</div>
</div>
</div>}
</div> </div>
) )
} }

+ 9
- 5
web/app/components/app-sidebar/index.tsx 查看文件

import React from 'react' import React from 'react'
import NavLink from './navLink' import NavLink from './navLink'
import AppBasic from './basic'

import type { NavIcon } from './navLink' import type { NavIcon } from './navLink'
import AppBasic from './basic'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


export type IAppDetailNavProps = { export type IAppDetailNavProps = {
iconType?: 'app' | 'dataset' | 'notion' iconType?: 'app' | 'dataset' | 'notion'
} }


const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => { const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => {
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const mode = isMobile ? 'collapse' : 'expand'

return ( return (
<div className="flex flex-col w-56 overflow-y-auto bg-white border-r border-gray-200 shrink-0">
<div className="flex flex-col sm:w-56 w-16 overflow-y-auto bg-white border-r border-gray-200 shrink-0 mobile:h-screen">
<div className="flex flex-shrink-0 p-4"> <div className="flex flex-shrink-0 p-4">
<AppBasic iconType={iconType} icon={icon} icon_background={icon_background} name={title} type={desc} />
<AppBasic mode={mode} iconType={iconType} icon={icon} icon_background={icon_background} name={title} type={desc} />
</div> </div>
<nav className="flex-1 p-4 space-y-1 bg-white"> <nav className="flex-1 p-4 space-y-1 bg-white">
{navigation.map((item, index) => { {navigation.map((item, index) => {
return ( return (
<NavLink key={index} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
<NavLink key={index} mode={mode} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
) )
})} })}
{extraInfo ?? null} {extraInfo ?? null}

+ 3
- 1
web/app/components/app-sidebar/navLink.tsx 查看文件

selected: NavIcon selected: NavIcon
normal: NavIcon normal: NavIcon
} }
mode?: 'expand' | 'collapse'
} }


export default function NavLink({ export default function NavLink({
name, name,
href, href,
iconMap, iconMap,
mode = 'expand',
}: NavLinkProps) { }: NavLinkProps) {
const segment = useSelectedLayoutSegment() const segment = useSelectedLayoutSegment()
const isActive = href.toLowerCase().split('/')?.pop() === segment?.toLowerCase() const isActive = href.toLowerCase().split('/')?.pop() === segment?.toLowerCase()
)} )}
aria-hidden="true" aria-hidden="true"
/> />
{name}
{mode === 'expand' && name}
</Link> </Link>
) )
} }

+ 10
- 5
web/app/components/app/configuration/config-model/index.tsx 查看文件

import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector' import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector'
import { ModelType, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations' import { ModelType, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import type { ModelModeType } from '@/types/app' import type { ModelModeType } from '@/types/app'
export type IConfigModelProps = { export type IConfigModelProps = {
isAdvancedMode: boolean isAdvancedMode: boolean
const [maxTokenSettingTipVisible, setMaxTokenSettingTipVisible] = useState(false) const [maxTokenSettingTipVisible, setMaxTokenSettingTipVisible] = useState(false)
const configContentRef = React.useRef(null) const configContentRef = React.useRef(null)
const currModel = textGenerationModelList.find(item => item.model_name === modelId) const currModel = textGenerationModelList.find(item => item.model_name === modelId)

const media = useBreakpoints()
const isMobile = media === MediaType.mobile

// Cache loaded model param // Cache loaded model param
const [allParams, setAllParams, getAllParams] = useGetState<Record<string, Record<string, any>>>({}) const [allParams, setAllParams, getAllParams] = useGetState<Record<string, Record<string, any>>>({})
const currParams = allParams[provider]?.[modelId] const currParams = allParams[provider]?.[modelId]
</div> </div>
{isShowConfig && ( {isShowConfig && (
<Panel <Panel
className='absolute z-20 top-8 right-0 !w-[496px] bg-white !overflow-visible shadow-md'
className='absolute z-20 top-8 left-0 sm:left-[unset] sm:right-0 !w-fit sm:!w-[496px] bg-white !overflow-visible shadow-md'
keepUnFold keepUnFold
headerIcon={ headerIcon={
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<div className='grow flex items-center' key={tone.id}> <div className='grow flex items-center' key={tone.id}>
<Radio <Radio
value={tone.id} value={tone.id}
className={cn(tone.id === toneId && 'rounded-md border border-gray-200 shadow-md', '!mr-0 grow !px-2 !justify-center text-[13px] font-medium')}
className={cn(tone.id === toneId && 'rounded-md border border-gray-200 shadow-md', '!mr-0 grow !px-1 sm:!px-2 !justify-center text-[13px] font-medium')}
labelClassName={cn(tone.id === toneId labelClassName={cn(tone.id === toneId
? ({ ? ({
1: 'text-[#6938EF]', 1: 'text-[#6938EF]',
> >
<> <>
{getToneIcon(tone.id)} {getToneIcon(tone.id)}
<div>{t(`common.model.tone.${tone.name}`) as string}</div>
{!isMobile && <div>{t(`common.model.tone.${tone.name}`) as string}</div>}
<div className=""></div> <div className=""></div>
</> </>
</Radio> </Radio>
</> </>
<Radio <Radio
value={TONE_LIST[3].id} value={TONE_LIST[3].id}
className={cn(toneId === 4 && 'rounded-md border border-gray-200 shadow-md', '!mr-0 grow !px-2 !justify-center text-[13px] font-medium')}
className={cn(toneId === 4 && 'rounded-md border border-gray-200 shadow-md', '!mr-0 grow !px-1 sm:!px-2 !justify-center text-[13px] font-medium')}
labelClassName={cn('flex items-center space-x-2 ', toneId === 4 ? 'text-[#155EEF]' : 'text-[#667085]')} labelClassName={cn('flex items-center space-x-2 ', toneId === 4 ? 'text-[#155EEF]' : 'text-[#667085]')}
> >
<> <>
{getToneIcon(TONE_LIST[3].id)} {getToneIcon(TONE_LIST[3].id)}
<div>{t(`common.model.tone.${TONE_LIST[3].name}`) as string}</div>
{!isMobile && <div>{t(`common.model.tone.${TONE_LIST[3].name}`) as string}</div>}
</> </>
</Radio> </Radio>
</Radio.Group> </Radio.Group>

+ 1
- 1
web/app/components/app/configuration/config-model/model-mode-type-label.tsx 查看文件



return ( return (
<div <div
className={cn(className, isHighlight ? 'border-indigo-300 text-indigo-600' : 'border-gray-300 text-gray-500', 'flex items-center h-4 px-1 border rounded text-xs font-semibold uppercase')}
className={cn(className, isHighlight ? 'border-indigo-300 text-indigo-600' : 'border-gray-300 text-gray-500', 'flex items-center h-4 px-1 border rounded text-xs font-semibold uppercase text-ellipsis overflow-hidden whitespace-nowrap')}
> >
{t(`appDebug.modelConfig.modeType.${type}`)} {t(`appDebug.modelConfig.modeType.${type}`)}
</div> </div>

+ 1
- 1
web/app/components/app/configuration/config-model/model-name.tsx 查看文件

modelDisplayName, modelDisplayName,
}) => { }) => {
return ( return (
<span title={modelDisplayName}>
<span className='text-ellipsis overflow-hidden whitespace-nowrap' title={modelDisplayName}>
{modelDisplayName} {modelDisplayName}
</span> </span>
) )

+ 1
- 1
web/app/components/app/configuration/config-model/param-item.tsx 查看文件

onChange(id, getFitPrecisionValue(value, precision)) onChange(id, getFitPrecisionValue(value, precision))
}, [value, precision]) }, [value, precision])
return ( return (
<div className="flex items-center justify-between">
<div className="flex items-center justify-between flex-wrap gap-y-2">
<div className="flex flex-col flex-shrink-0"> <div className="flex flex-col flex-shrink-0">
<div className="flex items-center"> <div className="flex items-center">
<span className="mr-[6px] text-gray-500 text-[13px] font-medium">{name}</span> <span className="mr-[6px] text-gray-500 text-[13px] font-medium">{name}</span>

+ 2
- 2
web/app/components/app/configuration/config-var/index.tsx 查看文件

<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.notSetVar')}</div> <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.notSetVar')}</div>
)} )}
{hasVar && ( {hasVar && (
<div className='rounded-lg border border-gray-200 bg-white'>
<table className={`${s.table} w-full border-collapse border-0 rounded-lg text-sm`}>
<div className='rounded-lg border border-gray-200 bg-white overflow-x-auto'>
<table className={`${s.table} min-w-[440px] max-w-full border-collapse border-0 rounded-lg text-sm`}>
<thead className="border-b border-gray-200 text-gray-500 text-xs font-medium"> <thead className="border-b border-gray-200 text-gray-500 text-xs font-medium">
<tr className='uppercase'> <tr className='uppercase'>
<td>{t('appDebug.variableTable.key')}</td> <td>{t('appDebug.variableTable.key')}</td>

+ 1
- 1
web/app/components/app/configuration/config-vision/param-config.tsx 查看文件

</div> </div>
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{ zIndex: 50 }}> <PortalToFollowElemContent style={{ zIndex: 50 }}>
<div className='w-[412px] p-4 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg space-y-3'>
<div className='w-80 sm:w-[412px] p-4 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg space-y-3'>
<ParamConfigContent /> <ParamConfigContent />
</div> </div>
</PortalToFollowElemContent> </PortalToFollowElemContent>

+ 30
- 5
web/app/components/app/configuration/config/automatic/get-automatic-res.tsx 查看文件

import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
// type // type
import type { AutomaticRes } from '@/service/debug' import type { AutomaticRes } from '@/service/debug'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


const noDataIcon = ( const noDataIcon = (
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()


const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const [audiences, setAudiences] = React.useState<string>('') const [audiences, setAudiences] = React.useState<string>('')
const [hopingToSolve, setHopingToSolve] = React.useState<string>('') const [hopingToSolve, setHopingToSolve] = React.useState<string>('')
const isValid = () => { const isValid = () => {


const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false) const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)


const isShowAutoPromptInput = () => {
if (isMobile) {
// hide prompt panel on mobile if it is loading or has had result
if (isLoading || res)
return false
return true
}

// alway display prompt panel on desktop mode
return true
}

const isShowAutoPromptResPlaceholder = () => {
if (isMobile) {
// hide placeholder panel on mobile
return false
}

return !isLoading && !res
}

return ( return (
<Modal <Modal
isShow={isShow} isShow={isShow}
onClose={onClose} onClose={onClose}
className='min-w-[1120px] !p-0'
className='!p-0 sm:min-w-[768px] xl:min-w-[1120px]'
closable closable
> >
<div className='flex h-[680px]'>
<div className='w-[480px] shrink-0 px-8 py-6 h-full overflow-y-auto border-r border-gray-100'>
<div className='flex h-[680px] flex-wrap gap-y-4 overflow-y-auto'>
{isShowAutoPromptInput() && <div className='w-full sm:w-[360px] xl:w-[480px] shrink-0 px-8 py-6 h-full overflow-y-auto border-r border-gray-100'>
<div> <div>
<div className='mb-1 text-xl font-semibold text-primary-600'>{t('appDebug.automatic.title')}</div> <div className='mb-1 text-xl font-semibold text-primary-600'>{t('appDebug.automatic.title')}</div>
<div className='text-[13px] font-normal text-gray-500'>{t('appDebug.automatic.description')}</div> <div className='text-[13px] font-normal text-gray-500'>{t('appDebug.automatic.description')}</div>
</Button> </Button>
</div> </div>
</div> </div>
</div>
</div>}


{(!isLoading && res) && ( {(!isLoading && res) && (
<div className='grow px-8 pt-6 h-full overflow-y-auto'> <div className='grow px-8 pt-6 h-full overflow-y-auto'>
</div> </div>
)} )}
{isLoading && renderLoading} {isLoading && renderLoading}
{(!isLoading && !res) && renderNoData}
{isShowAutoPromptResPlaceholder() && renderNoData}
{showConfirmOverwrite && ( {showConfirmOverwrite && (
<Confirm <Confirm
title={t('appDebug.automatic.overwriteTitle')} title={t('appDebug.automatic.overwriteTitle')}

+ 13
- 9
web/app/components/app/configuration/dataset-config/card-item/item.tsx 查看文件

import FileIcon from '@/app/components/base/file-icon' import FileIcon from '@/app/components/base/file-icon'
import { Settings01, Trash03 } from '@/app/components/base/icons/src/vender/line/general' import { Settings01, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
import { Folder } from '@/app/components/base/icons/src/vender/solid/files' import { Folder } from '@/app/components/base/icons/src/vender/solid/files'
import Drawer from '@/app/components/base/drawer'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


type ItemProps = { type ItemProps = {
className?: string className?: string
onRemove, onRemove,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()

const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const [showSettingsModal, setShowSettingsModal] = useState(false) const [showSettingsModal, setShowSettingsModal] = useState(false)


const handleSave = (newDataset: DataSet) => { const handleSave = (newDataset: DataSet) => {
<Trash03 className='w-4 h-4 text-gray-500 group-hover/action:text-[#D92D20]' /> <Trash03 className='w-4 h-4 text-gray-500 group-hover/action:text-[#D92D20]' />
</div> </div>
</div> </div>
{
showSettingsModal && (
<SettingsModal
currentDataset={config}
onCancel={() => setShowSettingsModal(false)}
onSave={handleSave}
/>
)
}
<Drawer isOpen={showSettingsModal} onClose={() => setShowSettingsModal(false)} footer={null} mask={isMobile} panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'>
<SettingsModal
currentDataset={config}
onCancel={() => setShowSettingsModal(false)}
onSave={handleSave}
/>
</Drawer>
</div> </div>
) )
} }

+ 1
- 1
web/app/components/app/configuration/dataset-config/params-config/index.tsx 查看文件

onClose={() => { onClose={() => {
setOpen(false) setOpen(false)
}} }}
className='min-w-[528px]'
className='sm:min-w-[528px]'
wrapperClassName='z-50' wrapperClassName='z-50'
title={t('appDebug.datasetConfig.settingTitle')} title={t('appDebug.datasetConfig.settingTitle')}
> >

+ 6
- 13
web/app/components/app/configuration/dataset-config/settings-modal/index.tsx 查看文件

import type { FC } from 'react' import type { FC } from 'react'
import { useRef, useState } from 'react' import { useRef, useState } from 'react'
import { useClickAway } from 'ahooks'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { isEqual } from 'lodash-es' import { isEqual } from 'lodash-es'
import cn from 'classnames' import cn from 'classnames'
} }


const rowClass = ` const rowClass = `
flex justify-between py-4
flex justify-between py-4 flex-wrap gap-y-2
` `


const labelClass = ` const labelClass = `
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useToastContext() const { notify } = useToastContext()
const ref = useRef(null) const ref = useRef(null)
useClickAway(() => {
if (ref)
onCancel()
}, ref)


const { setShowAccountSettingModal } = useModalContext() const { setShowAccountSettingModal } = useModalContext()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)


return ( return (
<div <div
className='fixed top-16 right-2 flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl z-10'
className='overflow-hidden w-full flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl'
style={{ style={{
zIndex: 11,
width: 700,
height: 'calc(100vh - 72px)', height: 'calc(100vh - 72px)',
}} }}
ref={ref} ref={ref}
<div className={labelClass}> <div className={labelClass}>
<div>{t('datasetSettings.form.permissions')}</div> <div>{t('datasetSettings.form.permissions')}</div>
</div> </div>
<div className='w-[480px]'>
<div className='w-full sm:w-[480px]'>
<PermissionsRadio <PermissionsRadio
disable={!localeCurrentDataset?.embedding_available} disable={!localeCurrentDataset?.embedding_available}
value={localeCurrentDataset.permission} value={localeCurrentDataset.permission}
onChange={v => handleValueChange('permission', v!)} onChange={v => handleValueChange('permission', v!)}
itemClassName='!w-[227px]'
itemClassName='sm:!w-[227px]'
/> />
</div> </div>
</div> </div>
disable={!localeCurrentDataset?.embedding_available} disable={!localeCurrentDataset?.embedding_available}
value={indexMethod} value={indexMethod}
onChange={v => setIndexMethod(v!)} onChange={v => setIndexMethod(v!)}
itemClassName='!w-[227px]'
itemClassName='sm:!w-[227px]'
/> />
</div> </div>
</div> </div>
)} )}


<div <div
className='absolute z-[5] bottom-0 w-full flex justify-end py-4 px-6 border-t bg-white '
className='sticky z-[5] bottom-0 w-full flex justify-end py-4 px-6 border-t bg-white '
style={{ style={{
borderColor: 'rgba(0, 0, 0, 0.05)', borderColor: 'rgba(0, 0, 0, 0.05)',
}} }}

+ 28
- 6
web/app/components/app/configuration/index.tsx 查看文件

import { useBoolean, useGetState } from 'ahooks' import { useBoolean, useGetState } from 'ahooks'
import cn from 'classnames' import cn from 'classnames'
import { clone, isEqual } from 'lodash-es' import { clone, isEqual } from 'lodash-es'
import { CodeBracketIcon } from '@heroicons/react/20/solid'
import Button from '../../base/button' import Button from '../../base/button'
import Loading from '../../base/loading' import Loading from '../../base/loading'
import s from './style.module.css' import s from './style.module.css'
import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset' import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset'
import I18n from '@/context/i18n' import I18n from '@/context/i18n'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Drawer from '@/app/components/base/drawer'


type PublichConfig = { type PublichConfig = {
modelConfig: ModelConfig modelConfig: ModelConfig


const [conversationId, setConversationId] = useState<string | null>('') const [conversationId, setConversationId] = useState<string | null>('')


const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const [isShowDebugPanel, { setTrue: showDebugPanel, setFalse: hideDebugPanel }] = useBoolean(false)

const [introduction, setIntroduction] = useState<string>('') const [introduction, setIntroduction] = useState<string>('')
const [controlClearChatMessage, setControlClearChatMessage] = useState(0) const [controlClearChatMessage, setControlClearChatMessage] = useState(0)
const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({ const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({
> >
<> <>
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className='flex items-center justify-between px-6 shrink-0 h-14'>
<div className='flex items-center justify-between px-6 shrink-0 py-3 flex-wrap gap-y-2'>
<div className='flex items-end'> <div className='flex items-end'>
<div className={s.promptTitle}></div> <div className={s.promptTitle}></div>
<div className='flex items-center h-[14px] space-x-1 text-xs'> <div className='flex items-center h-[14px] space-x-1 text-xs'>
</div> </div>
</div> </div>


<div className='flex items-center'>
<div className='flex items-center flex-wrap gap-y-2 gap-x-2'>
{/* Model and Parameters */} {/* Model and Parameters */}
<ConfigModel <ConfigModel
isAdvancedMode={isAdvancedMode} isAdvancedMode={isAdvancedMode}
}} }}
disabled={!hasSetAPIKEY} disabled={!hasSetAPIKEY}
/> />
<div className='mx-3 w-[1px] h-[14px] bg-gray-200'></div>
<div className='w-[1px] h-[14px] bg-gray-200'></div>
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button> <Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
{isMobile && (
<Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
<span className='mr-1'>{t('appDebug.operation.debugConfig')}</span>
<CodeBracketIcon className="h-4 w-4 text-gray-500" />
</Button>
)}
<Button type='primary' onClick={() => handlePublish(false)} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button> <Button type='primary' onClick={() => handlePublish(false)} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
</div> </div>
</div> </div>
<div className='flex grow h-[200px]'> <div className='flex grow h-[200px]'>
<div className="w-1/2 min-w-[560px] shrink-0">
<div className="w-full sm:w-1/2 shrink-0">
<Config /> <Config />
</div> </div>
<div className="relative w-1/2 grow h-full overflow-y-auto py-4 px-6 bg-gray-50 flex flex-col rounded-tl-2xl border-t border-l" style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
{!isMobile && <div className="relative w-1/2 grow h-full overflow-y-auto py-4 px-6 bg-gray-50 flex flex-col rounded-tl-2xl border-t border-l" style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<Debug <Debug
hasSetAPIKEY={hasSetAPIKEY} hasSetAPIKEY={hasSetAPIKEY}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })} onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
inputs={inputs} inputs={inputs}
/> />
</div>
</div>}
</div> </div>
</div> </div>
{showConfirm && ( {showConfirm && (
}} }}
/> />
)} )}
{isMobile && (
<Drawer showClose isOpen={isShowDebugPanel} onClose={hideDebugPanel} mask footer={null} panelClassname='!bg-gray-50'>
<Debug
hasSetAPIKEY={hasSetAPIKEY}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
inputs={inputs}
/>
</Drawer>
)}
</> </>
</ConfigContext.Provider> </ConfigContext.Provider>
) )

+ 2
- 2
web/app/components/app/log/filter.tsx 查看文件

if (!data) if (!data)
return null return null
return ( return (
<div className='flex flex-row items-center mb-4 text-gray-900 text-base'>
<div className='flex flex-row flex-wrap gap-y-2 gap-x-4 items-center mb-4 text-gray-900 text-base'>
<SimpleSelect <SimpleSelect
items={TIME_PERIOD_LIST.map(item => ({ value: item.value, name: t(`appLog.filter.period.${item.name}`) }))} items={TIME_PERIOD_LIST.map(item => ({ value: item.value, name: t(`appLog.filter.period.${item.name}`) }))}
className='mt-0 !w-40' className='mt-0 !w-40'
setQueryParams({ ...queryParams, period: item.value }) setQueryParams({ ...queryParams, period: item.value })
}} }}
defaultValue={queryParams.period} /> defaultValue={queryParams.period} />
<div className="relative ml-4 rounded-md mr-4">
<div className="relative rounded-md">
<SimpleSelect <SimpleSelect
defaultValue={'all'} defaultValue={'all'}
className='!w-[300px]' className='!w-[300px]'

+ 19
- 14
web/app/components/app/log/list.tsx 查看文件

import ModelIcon from '@/app/components/app/configuration/config-model/model-icon' import ModelIcon from '@/app/components/app/configuration/config-model/model-icon'
import ModelName from '@/app/components/app/configuration/config-model/model-name' import ModelName from '@/app/components/app/configuration/config-model/model-name'
import ModelModeTypeLabel from '@/app/components/app/configuration/config-model/model-mode-type-label' import ModelModeTypeLabel from '@/app/components/app/configuration/config-model/model-mode-type-label'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


type IConversationList = { type IConversationList = {
logs?: ChatConversationsResponse | CompletionConversationsResponse logs?: ChatConversationsResponse | CompletionConversationsResponse
<div className='text-gray-500 text-[10px] leading-[14px]'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div> <div className='text-gray-500 text-[10px] leading-[14px]'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div>
<div className='text-gray-700 text-[13px] leading-[18px]'>{isChatMode ? detail.id?.split('-').slice(-1)[0] : dayjs.unix(detail.created_at).format(t('appLog.dateTimeFormat') as string)}</div> <div className='text-gray-700 text-[13px] leading-[18px]'>{isChatMode ? detail.id?.split('-').slice(-1)[0] : dayjs.unix(detail.created_at).format(t('appLog.dateTimeFormat') as string)}</div>
</div> </div>
<div className='flex items-center'>
<div className='flex items-center flex-wrap gap-y-1 justify-end'>
<div <div
className={cn('mr-2 flex items-center border h-8 px-2 space-x-2 rounded-lg bg-indigo-25 border-[#2A87F5]')} className={cn('mr-2 flex items-center border h-8 px-2 space-x-2 rounded-lg bg-indigo-25 border-[#2A87F5]')}
> >
*/ */
const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh }) => { const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh }) => {
const { t } = useTranslation() const { t } = useTranslation()

const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const [showDrawer, setShowDrawer] = useState<boolean>(false) // Whether to display the chat details drawer const [showDrawer, setShowDrawer] = useState<boolean>(false) // Whether to display the chat details drawer
const [currentConversation, setCurrentConversation] = useState<ChatConversationGeneralDetail | CompletionConversationGeneralDetail | undefined>() // Currently selected conversation const [currentConversation, setCurrentConversation] = useState<ChatConversationGeneralDetail | CompletionConversationGeneralDetail | undefined>() // Currently selected conversation
const isChatMode = appDetail?.mode === 'chat' // Whether the app is a chat app const isChatMode = appDetail?.mode === 'chat' // Whether the app is a chat app
return <Loading /> return <Loading />


return ( return (
<>
<table className={`w-full border-collapse border-0 text-sm mt-3 ${s.logTable}`}>
<thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-bold">
<div className='overflow-x-auto'>
<table className={`w-full min-w-[440px] border-collapse border-0 text-sm mt-3 ${s.logTable}`}>
<thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-bold">
<tr> <tr>
<td className='w-[1.375rem]'></td>
<td>{t('appLog.table.header.time')}</td>
<td>{t('appLog.table.header.endUser')}</td>
<td>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td>
<td>{isChatMode ? t('appLog.table.header.messageCount') : t('appLog.table.header.output')}</td>
<td>{t('appLog.table.header.userRate')}</td>
<td>{t('appLog.table.header.adminRate')}</td>
<td className='w-[1.375rem] whitespace-nowrap'></td>
<td className='whitespace-nowrap'>{t('appLog.table.header.time')}</td>
<td className='whitespace-nowrap'>{t('appLog.table.header.endUser')}</td>
<td className='whitespace-nowrap'>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td>
<td className='whitespace-nowrap'>{isChatMode ? t('appLog.table.header.messageCount') : t('appLog.table.header.output')}</td>
<td className='whitespace-nowrap'>{t('appLog.table.header.userRate')}</td>
<td className='whitespace-nowrap'>{t('appLog.table.header.adminRate')}</td>
</tr> </tr>
</thead> </thead>
<tbody className="text-gray-500"> <tbody className="text-gray-500">
<Drawer <Drawer
isOpen={showDrawer} isOpen={showDrawer}
onClose={onCloseDrawer} onClose={onCloseDrawer}
mask={false}
mask={isMobile}
footer={null} footer={null}
panelClassname='mt-16 mr-2 mb-3 !p-0 !max-w-[640px] rounded-b-xl'
panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'
> >
<DrawerContext.Provider value={{ <DrawerContext.Provider value={{
onClose: onCloseDrawer, onClose: onCloseDrawer,
} }
</DrawerContext.Provider> </DrawerContext.Provider>
</Drawer> </Drawer>
</>
</div>
) )
} }



+ 4
- 4
web/app/components/app/overview/appCard.tsx 查看文件



return ( return (
<div <div
className={`min-w-max shadow-xs border-[0.5px] rounded-lg border-gray-200 ${
className={`shadow-xs border-[0.5px] rounded-lg border-gray-200 ${
className ?? '' className ?? ''
}`} }`}
> >
: t('appOverview.overview.apiInfo.accessibleAddress')} : t('appOverview.overview.apiInfo.accessibleAddress')}
</div> </div>
<div className="w-full h-9 pl-2 pr-0.5 py-0.5 bg-black bg-opacity-[0.02] rounded-lg border border-black border-opacity-5 justify-start items-center inline-flex"> <div className="w-full h-9 pl-2 pr-0.5 py-0.5 bg-black bg-opacity-[0.02] rounded-lg border border-black border-opacity-5 justify-start items-center inline-flex">
<div className="h-4 px-2 justify-start items-start gap-2 flex flex-1">
<div className="text-gray-700 text-xs font-medium">
<div className="h-4 px-2 justify-start items-start gap-2 flex flex-1 min-w-0">
<div className="text-gray-700 text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap">
{isApp ? appUrl : apiUrl} {isApp ? appUrl : apiUrl}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className={'pt-2 flex flex-row items-center'}>
<div className={'pt-2 flex flex-row items-center flex-wrap gap-y-2'}>
{!isApp && <SecretKeyButton className='flex-shrink-0 !h-8 bg-white mr-2' textCls='!text-gray-700 font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />} {!isApp && <SecretKeyButton className='flex-shrink-0 !h-8 bg-white mr-2' textCls='!text-gray-700 font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />}
{OPERATIONS_MAP[cardType].map((op) => { {OPERATIONS_MAP[cardType].map((op) => {
const disabled const disabled

+ 2
- 2
web/app/components/app/overview/customize/index.tsx 查看文件

</div> </div>
<div className='flex py-4'> <div className='flex py-4'>
<StepNum>3</StepNum> <StepNum>3</StepNum>
<div className='flex flex-col w-full'>
<div className='flex flex-col w-full overflow-hidden'>
<div className='text-gray-900'>{t(`${prefixCustomize}.way1.step3`)}</div> <div className='text-gray-900'>{t(`${prefixCustomize}.way1.step3`)}</div>
<div className='text-gray-500 text-xs mt-1 mb-2'>{t(`${prefixCustomize}.way1.step3Tip`)}</div> <div className='text-gray-500 text-xs mt-1 mb-2'>{t(`${prefixCustomize}.way1.step3Tip`)}</div>
<pre className='box-border py-3 px-4 bg-gray-100 text-xs font-medium rounded-lg select-text'>
<pre className='overflow-x-scroll box-border py-3 px-4 bg-gray-100 text-xs font-medium rounded-lg select-text'>
NEXT_PUBLIC_APP_ID={`'${appId}'`} <br /> NEXT_PUBLIC_APP_ID={`'${appId}'`} <br />
NEXT_PUBLIC_APP_KEY={'\'<Web API Key From Dify>\''} <br /> NEXT_PUBLIC_APP_KEY={'\'<Web API Key From Dify>\''} <br />
NEXT_PUBLIC_API_URL={`'${api_base_url}'`} NEXT_PUBLIC_API_URL={`'${api_base_url}'`}

+ 2
- 2
web/app/components/app/overview/embedded/index.tsx 查看文件

<div className="mb-4 mt-8 text-gray-900 text-[14px] font-medium leading-tight"> <div className="mb-4 mt-8 text-gray-900 text-[14px] font-medium leading-tight">
{t(`${prefixEmbedded}.explanation`)} {t(`${prefixEmbedded}.explanation`)}
</div> </div>
<div className="flex items-center justify-between">
<div className="flex items-center justify-between flex-wrap gap-y-2">
{Object.keys(OPTION_MAP).map((v, index) => { {Object.keys(OPTION_MAP).map((v, index) => {
return ( return (
<div <div
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
<div className="self-stretch p-3 justify-start items-start gap-2 inline-flex">
<div className="p-3 justify-start items-start gap-2 flex overflow-x-auto w-full">
<div className="grow shrink basis-0 text-slate-700 text-[13px] leading-tight font-mono"> <div className="grow shrink basis-0 text-slate-700 text-[13px] leading-tight font-mono">
<pre className='select-text'>{OPTION_MAP[option].getContent(appBaseUrl, accessToken, isTestEnv)}</pre> <pre className='select-text'>{OPTION_MAP[option].getContent(appBaseUrl, accessToken, isTestEnv)}</pre>
</div> </div>

+ 8
- 2
web/app/components/base/drawer/index.tsx 查看文件

'use client' 'use client'
import { Dialog } from '@headlessui/react' import { Dialog } from '@headlessui/react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { XMarkIcon } from '@heroicons/react/24/outline'
import Button from '../button' import Button from '../button'


type DrawerProps = {
export type IDrawerProps = {
title?: string title?: string
description?: string description?: string
panelClassname?: string panelClassname?: string
mask?: boolean mask?: boolean
isOpen: boolean isOpen: boolean
// closable: boolean // closable: boolean
showClose?: boolean
onClose: () => void onClose: () => void
onCancel?: () => void onCancel?: () => void
onOk?: () => void onOk?: () => void
children, children,
footer, footer,
mask = true, mask = true,
showClose = false,
isOpen, isOpen,
onClose, onClose,
onCancel, onCancel,
onOk, onOk,
}: DrawerProps) {
}: IDrawerProps) {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<Dialog <Dialog
> >
{title} {title}
</Dialog.Title>} </Dialog.Title>}
{showClose && <Dialog.Title className="flex items-center mb-4" as="div">
<XMarkIcon className='w-4 h-4 text-gray-500' onClick={onClose} />
</Dialog.Title>}
{description && <Dialog.Description className='text-gray-500 text-xs font-normal mt-2'>{description}</Dialog.Description>} {description && <Dialog.Description className='text-gray-500 text-xs font-normal mt-2'>{description}</Dialog.Description>}
{children} {children}
</> </>

+ 37
- 0
web/app/components/base/float-popover-container/index.tsx 查看文件

'use client'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import type { PortalToFollowElemOptions } from '@/app/components/base/portal-to-follow-elem'

type IFloatRightContainerProps = {
isMobile: boolean
open: boolean
toggle: () => void
triggerElement?: React.ReactNode
children?: React.ReactNode
} & PortalToFollowElemOptions

const FloatRightContainer = ({ open, toggle, triggerElement, isMobile, children, ...portalProps }: IFloatRightContainerProps) => {
return (
<>
{isMobile && (
<PortalToFollowElem open={open} {...portalProps}>
<PortalToFollowElemTrigger onClick={toggle}>
{triggerElement}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
{children}
</PortalToFollowElemContent>
</PortalToFollowElem>
)}
{!isMobile && open && (
<>{children}</>
)}
</>
)
}

export default FloatRightContainer

+ 23
- 0
web/app/components/base/float-right-container/index.tsx 查看文件

'use client'
import Drawer from '@/app/components/base/drawer'
import type { IDrawerProps } from '@/app/components/base/drawer'

type IFloatRightContainerProps = {
isMobile: boolean
children?: React.ReactNode
} & IDrawerProps

const FloatRightContainer = ({ isMobile, children, isOpen, ...drawerProps }: IFloatRightContainerProps) => {
return (
<>
{isMobile && (
<Drawer isOpen={isOpen} {...drawerProps}>{children}</Drawer>
)}
{(!isMobile && isOpen) && (
<>{children}</>
)}
</>
)
}

export default FloatRightContainer

+ 1
- 1
web/app/components/base/popover/style.module.css 查看文件

@apply absolute z-10 w-full max-w-sm px-4 mt-1 sm:px-0 lg:max-w-3xl @apply absolute z-10 w-full max-w-sm px-4 mt-1 sm:px-0 lg:max-w-3xl
} }
.panelContainer { .panelContainer {
@apply overflow-hidden bg-white w-full rounded-lg shadow-lg ring-1 ring-black ring-opacity-5
@apply overflow-hidden bg-white w-fit min-w-[130px] rounded-lg shadow-lg ring-1 ring-black ring-opacity-5
} }

+ 1
- 1
web/app/components/base/portal-to-follow-elem/index.tsx 查看文件



import type { OffsetOptions, Placement } from '@floating-ui/react' import type { OffsetOptions, Placement } from '@floating-ui/react'


type PortalToFollowElemOptions = {
export type PortalToFollowElemOptions = {
/* /*
* top, bottom, left, right * top, bottom, left, right
* start, end. Default is middle * start, end. Default is middle

+ 1
- 1
web/app/components/base/select/index.tsx 查看文件



const SimpleSelect: FC<ISelectProps> = ({ const SimpleSelect: FC<ISelectProps> = ({
className, className,
wrapperClassName,
wrapperClassName = '',
items = defaultItems, items = defaultItems,
defaultValue = 1, defaultValue = 1,
disabled = false, disabled = false,

+ 2
- 2
web/app/components/datasets/create/file-uploader/index.module.css 查看文件

color: #667085; color: #667085;
} }
.uploader { .uploader {
@apply relative box-border flex justify-center items-center mb-2;
@apply relative box-border flex justify-center items-center mb-2 p-3;
flex-direction: column; flex-direction: column;
max-width: 640px; max-width: 640px;
height: 80px;
min-height: 80px;
background: #F9FAFB; background: #F9FAFB;
border: 1px dashed #EAECF0; border: 1px dashed #EAECF0;
border-radius: 12px; border-radius: 12px;

+ 5
- 3
web/app/components/datasets/create/file-uploader/index.tsx 查看文件

/> />
<div className={cn(s.title, titleClassName)}>{t('datasetCreation.stepOne.uploader.title')}</div> <div className={cn(s.title, titleClassName)}>{t('datasetCreation.stepOne.uploader.title')}</div>
<div ref={dropRef} className={cn(s.uploader, dragging && s.dragging)}> <div ref={dropRef} className={cn(s.uploader, dragging && s.dragging)}>
<div className='flex justify-center items-center h-6 mb-2'>
<div className='flex justify-center items-center min-h-6 mb-2'>
<span className={s.uploadIcon}/> <span className={s.uploadIcon}/>
<span>{t('datasetCreation.stepOne.uploader.button')}</span>
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
<span>
{t('datasetCreation.stepOne.uploader.button')}
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
</span>
</div> </div>
<div className={s.tip}>{t('datasetCreation.stepOne.uploader.tip', { size: fileUploadConfig.file_size_limit })}</div> <div className={s.tip}>{t('datasetCreation.stepOne.uploader.tip', { size: fileUploadConfig.file_size_limit })}</div>
{dragging && <div ref={dragRef} className={s.draggingCover}/>} {dragging && <div ref={dragRef} className={s.draggingCover}/>}

+ 1
- 1
web/app/components/datasets/create/index.tsx 查看文件



return ( return (
<div className='flex' style={{ height: 'calc(100vh - 56px)' }}> <div className='flex' style={{ height: 'calc(100vh - 56px)' }}>
<div className="flex flex-col w-56 overflow-y-auto bg-white border-r border-gray-200 shrink-0">
<div className="flex flex-col w-11 sm:w-56 overflow-y-auto bg-white border-r border-gray-200 shrink-0">
<StepsNavBar step={step} datasetId={datasetId} /> <StepsNavBar step={step} datasetId={datasetId} />
</div> </div>
<div className="grow bg-white"> <div className="grow bg-white">

+ 0
- 3
web/app/components/datasets/create/step-one/index.module.css 查看文件

background-color: #fff; background-color: #fff;
} }


.dataSourceTypeList {
@apply flex items-center mb-8;
}
.dataSourceItem { .dataSourceItem {
@apply box-border relative shrink-0 flex items-center mr-3 p-3 h-14 bg-white rounded-xl cursor-pointer; @apply box-border relative shrink-0 flex items-center mr-3 p-3 h-14 bg-white rounded-xl cursor-pointer;
border: 0.5px solid #EAECF0; border: 0.5px solid #EAECF0;

+ 1
- 1
web/app/components/datasets/create/step-one/index.tsx 查看文件

<div className={s.form}> <div className={s.form}>
{ {
shouldShowDataSourceTypeList && ( shouldShowDataSourceTypeList && (
<div className={s.dataSourceTypeList}>
<div className='flex items-center mb-8 flex-wrap gap-y-4'>
<div <div
className={cn( className={cn(
s.dataSourceItem, s.dataSourceItem,

+ 7
- 3
web/app/components/datasets/create/step-three/index.tsx 查看文件

import EmbeddingProcess from '../embedding-process' import EmbeddingProcess from '../embedding-process'


import s from './index.module.css' import s from './index.module.css'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import type { FullDocumentDetail, createDocumentResponse } from '@/models/datasets' import type { FullDocumentDetail, createDocumentResponse } from '@/models/datasets'


type StepThreeProps = { type StepThreeProps = {
const StepThree = ({ datasetId, datasetName, indexingType, creationCache }: StepThreeProps) => { const StepThree = ({ datasetId, datasetName, indexingType, creationCache }: StepThreeProps) => {
const { t } = useTranslation() const { t } = useTranslation()


const media = useBreakpoints()
const isMobile = media === MediaType.mobile

return ( return (
<div className='flex w-full h-full'> <div className='flex w-full h-full'>
<div className={'h-full w-full overflow-y-scroll px-16'}>
<div className={'h-full w-full overflow-y-scroll px-6 sm:px-16'}>
<div className='max-w-[636px]'> <div className='max-w-[636px]'>
{!datasetId && ( {!datasetId && (
<> <>
/> />
</div> </div>
</div> </div>
<div className={cn(s.sideTip)}>
{!isMobile && <div className={cn(s.sideTip)}>
<div className={s.tipCard}> <div className={s.tipCard}>
<span className={s.icon}/> <span className={s.icon}/>
<div className={s.title}>{t('datasetCreation.stepThree.sideTipTitle')}</div> <div className={s.title}>{t('datasetCreation.stepThree.sideTipTitle')}</div>
<div className={s.content}>{t('datasetCreation.stepThree.sideTipContent')}</div> <div className={s.content}>{t('datasetCreation.stepThree.sideTipContent')}</div>
</div> </div>
</div>
</div>}
</div> </div>
) )
} }

+ 3
- 3
web/app/components/datasets/create/step-two/index.module.css 查看文件

.pageHeader { .pageHeader {
@apply px-16;
@apply px-16 flex justify-between items-center;
position: sticky; position: sticky;
top: 0; top: 0;
left: 0; left: 0;
} }


.ruleItem { .ruleItem {
@apply flex items-center h-7;
@apply flex items-center;
} }


.formFooter { .formFooter {


.previewWrap { .previewWrap {
flex-shrink: 0; flex-shrink: 0;
width: 524px;
max-width: 524px;
} }


.previewHeader { .previewHeader {

+ 85
- 62
web/app/components/datasets/create/step-two/index.tsx 查看文件

import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { XMarkIcon } from '@heroicons/react/20/solid' import { XMarkIcon } from '@heroicons/react/20/solid'
import { RocketLaunchIcon } from '@heroicons/react/24/outline'
import cn from 'classnames' import cn from 'classnames'
import Link from 'next/link' import Link from 'next/link'
import { groupBy } from 'lodash-es' import { groupBy } from 'lodash-es'
} from '@/service/datasets' } from '@/service/datasets'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import FloatRightContainer from '@/app/components/base/float-right-container'
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config' import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
import { type RetrievalConfig } from '@/types/app' import { type RetrievalConfig } from '@/types/app'
import { IS_CE_EDITION } from '@/config' import { IS_CE_EDITION } from '@/config'
import { RETRIEVE_METHOD } from '@/types/app' import { RETRIEVE_METHOD } from '@/types/app'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Tooltip from '@/app/components/base/tooltip'


type ValueOf<T> = T[keyof T] type ValueOf<T> = T[keyof T]
type StepTwoProps = { type StepTwoProps = {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const { locale } = useContext(I18n)


const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const { dataset: currentDataset, mutateDatasetRes } = useDatasetDetailContext() const { dataset: currentDataset, mutateDatasetRes } = useDatasetDetailContext()
const scrollRef = useRef<HTMLDivElement>(null) const scrollRef = useRef<HTMLDivElement>(null)
const [scrolled, setScrolled] = useState(false) const [scrolled, setScrolled] = useState(false)
useEffect(() => { useEffect(() => {
if (segmentationType === SegmentType.AUTO) { if (segmentationType === SegmentType.AUTO) {
setAutomaticFileIndexingEstimate(null) setAutomaticFileIndexingEstimate(null)
setShowPreview()
!isMobile && setShowPreview()
fetchFileIndexingEstimate() fetchFileIndexingEstimate()
setPreviewSwitched(false) setPreviewSwitched(false)
} }
return ( return (
<div className='flex w-full h-full'> <div className='flex w-full h-full'>
<div ref={scrollRef} className='relative h-full w-full overflow-y-scroll'> <div ref={scrollRef} className='relative h-full w-full overflow-y-scroll'>
<div className={cn(s.pageHeader, scrolled && s.fixed)}>{t('datasetCreation.steps.two')}</div>
<div className={cn(s.form)}>
<div className={cn(s.pageHeader, scrolled && s.fixed, isMobile && '!px-6')}>
<span>{t('datasetCreation.steps.two')}</span>
{isMobile && (
<Button
className='border-[0.5px] !h-8 hover:outline hover:outline-[0.5px] hover:outline-gray-300 text-gray-700 font-medium bg-white shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)]'
onClick={setShowPreview}
>
<Tooltip selector='data-preview-toggle'>
<div className="flex flex-row items-center">
<RocketLaunchIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" />
<span className="text-[13px]">{t('datasetCreation.stepTwo.previewTitleButton')}</span>
</div>
</Tooltip>
</Button>
)}
</div>
<div className={cn(s.form, isMobile && '!px-4')}>
<div className={s.label}>{t('datasetCreation.stepTwo.segmentation')}</div> <div className={s.label}>{t('datasetCreation.stepTwo.segmentation')}</div>
<div className='max-w-[640px]'> <div className='max-w-[640px]'>
<div <div
</div> </div>
</div> </div>
<div className={s.formRow}> <div className={s.formRow}>
<div className='w-full'>
<div className='w-full flex flex-col gap-1'>
<div className={s.label}>{t('datasetCreation.stepTwo.rules')}</div> <div className={s.label}>{t('datasetCreation.stepTwo.rules')}</div>
{rules.map(rule => ( {rules.map(rule => (
<div key={rule.id} className={s.ruleItem}> <div key={rule.id} className={s.ruleItem}>
</div> </div>
<div className={s.label}>{t('datasetCreation.stepTwo.indexMode')}</div> <div className={s.label}>{t('datasetCreation.stepTwo.indexMode')}</div>
<div className='max-w-[640px]'> <div className='max-w-[640px]'>
<div className='flex items-center gap-3'>
<div className='flex items-center gap-3 flex-wrap sm:flex-nowrap'>
{(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.QUALIFIED)) && ( {(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.QUALIFIED)) && (
<div <div
className={cn( className={cn(
</div> </div>
</div> </div>
</div> </div>
{(showPreview)
? (
<div ref={previewScrollRef} className={cn(s.previewWrap, 'relativeh-full overflow-y-scroll border-l border-[#F2F4F7]')}>
<div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`)}>
<div className='flex items-center justify-between px-8'>
<div className='grow flex items-center'>
<div>{t('datasetCreation.stepTwo.previewTitle')}</div>
{docForm === DocForm.QA && !previewSwitched && (
<Button className='ml-2 !h-[26px] !py-[3px] !px-2 !text-xs !font-medium !text-primary-600' onClick={previewSwitch}>{t('datasetCreation.stepTwo.previewButton')}</Button>
)}
</div>
<div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
<XMarkIcon className='h-4 w-4'></XMarkIcon>
</div>
<FloatRightContainer isMobile={isMobile} isOpen={showPreview} onClose={hidePreview} footer={null}>
{showPreview && <div ref={previewScrollRef} className={cn(s.previewWrap, 'relative h-full overflow-y-scroll border-l border-[#F2F4F7]')}>
<div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`)}>
<div className='flex items-center justify-between px-8'>
<div className='grow flex items-center'>
<div>{t('datasetCreation.stepTwo.previewTitle')}</div>
{docForm === DocForm.QA && !previewSwitched && (
<Button className='ml-2 !h-[26px] !py-[3px] !px-2 !text-xs !font-medium !text-primary-600' onClick={previewSwitch}>{t('datasetCreation.stepTwo.previewButton')}</Button>
)}
</div>
<div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
<XMarkIcon className='h-4 w-4'></XMarkIcon>
</div> </div>
{docForm === DocForm.QA && !previewSwitched && (
<div className='px-8 pr-12 text-xs text-gray-500'>
<span>{t('datasetCreation.stepTwo.previewSwitchTipStart')}</span>
<span className='text-amber-600'>{t('datasetCreation.stepTwo.previewSwitchTipEnd')}</span>
</div>
)}
</div>
<div className='my-4 px-8 space-y-4'>
{previewSwitched && docForm === DocForm.QA && fileIndexingEstimate?.qa_preview && (
<>
{fileIndexingEstimate?.qa_preview.map((item, index) => (
<PreviewItem type={PreviewType.QA} key={item.question} qa={item} index={index + 1} />
))}
</>
)}
{(docForm === DocForm.TEXT || !previewSwitched) && fileIndexingEstimate?.preview && (
<>
{fileIndexingEstimate?.preview.map((item, index) => (
<PreviewItem type={PreviewType.TEXT} key={item} content={item} index={index + 1} />
))}
</>
)}
{previewSwitched && docForm === DocForm.QA && !fileIndexingEstimate?.qa_preview && (
<div className='flex items-center justify-center h-[200px]'>
<Loading type='area' />
</div>
)}
{!previewSwitched && !fileIndexingEstimate?.preview && (
<div className='flex items-center justify-center h-[200px]'>
<Loading type='area' />
</div>
)}
</div> </div>
{docForm === DocForm.QA && !previewSwitched && (
<div className='px-8 pr-12 text-xs text-gray-500'>
<span>{t('datasetCreation.stepTwo.previewSwitchTipStart')}</span>
<span className='text-amber-600'>{t('datasetCreation.stepTwo.previewSwitchTipEnd')}</span>
</div>
)}
</div>
<div className='my-4 px-8 space-y-4'>
{previewSwitched && docForm === DocForm.QA && fileIndexingEstimate?.qa_preview && (
<>
{fileIndexingEstimate?.qa_preview.map((item, index) => (
<PreviewItem type={PreviewType.QA} key={item.question} qa={item} index={index + 1} />
))}
</>
)}
{(docForm === DocForm.TEXT || !previewSwitched) && fileIndexingEstimate?.preview && (
<>
{fileIndexingEstimate?.preview.map((item, index) => (
<PreviewItem type={PreviewType.TEXT} key={item} content={item} index={index + 1} />
))}
</>
)}
{previewSwitched && docForm === DocForm.QA && !fileIndexingEstimate?.qa_preview && (
<div className='flex items-center justify-center h-[200px]'>
<Loading type='area' />
</div>
)}
{!previewSwitched && !fileIndexingEstimate?.preview && (
<div className='flex items-center justify-center h-[200px]'>
<Loading type='area' />
</div>
)}
</div> </div>
)
: (<div className={cn(s.sideTip)}>
<div className={s.tipCard}>
<span className={s.icon} />
<div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div>
<div className={s.content}>
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP1')}</p>
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP2')}</p>
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP3')}</p>
<p>{t('datasetCreation.stepTwo.sideTipP4')}</p>
</div>}
{!showPreview && (
<div className={cn(s.sideTip)}>
<div className={s.tipCard}>
<span className={s.icon} />
<div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div>
<div className={s.content}>
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP1')}</p>
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP2')}</p>
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP3')}</p>
<p>{t('datasetCreation.stepTwo.sideTipP4')}</p>
</div>
</div> </div>
</div> </div>
</div>)}
)}
</FloatRightContainer>
</div> </div>
) )
} }

+ 3
- 2
web/app/components/datasets/create/steps-nav-bar/index.module.css 查看文件

background-size: 16px; background-size: 16px;
} }
.stepList { .stepList {
@apply p-4;
@apply p-4 relative;
line-height: 18px; line-height: 18px;
} }


.stepItem { .stepItem {
@apply relative flex justify-items-start pt-3 pr-0 pb-3;
@apply relative flex justify-items-start pt-3 pr-0 pb-3 box-content;
padding-left: 52px; padding-left: 52px;
font-size: 13px; font-size: 13px;
height: 18px;
} }


.stepItem.step1::before { .stepItem.step1::before {

+ 33
- 23
web/app/components/datasets/create/steps-nav-bar/index.tsx 查看文件

import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'


import cn from 'classnames' import cn from 'classnames'
import { useCallback } from 'react'
import s from './index.module.css' import s from './index.module.css'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


type IStepsNavBarProps = { type IStepsNavBarProps = {
step: number,
datasetId?: string,
step: number
datasetId?: string
} }


const STEP_T_MAP: Record<number, string> = {
1: 'datasetCreation.steps.one',
2: 'datasetCreation.steps.two',
3: 'datasetCreation.steps.three',
}

const STEP_LIST = [1, 2, 3]

const StepsNavBar = ({ const StepsNavBar = ({
step, step,
datasetId, datasetId,
}: IStepsNavBarProps) => { }: IStepsNavBarProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter() const router = useRouter()
const navBackHandle = () => {
if (!datasetId) {

const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const navBackHandle = useCallback(() => {
if (!datasetId)
router.replace('/datasets') router.replace('/datasets')
} else {
else
router.replace(`/datasets/${datasetId}/documents`) router.replace(`/datasets/${datasetId}/documents`)
}
}
}, [router, datasetId])


return ( return (
<div className='w-full pt-4'> <div className='w-full pt-4'>
<div className={s.stepsHeader}>
<div onClick={navBackHandle} className={s.navBack} />
{!datasetId ? t('datasetCreation.steps.header.creation') : t('datasetCreation.steps.header.update')}
<div className={cn(s.stepsHeader, isMobile && '!px-0 justify-center')}>
<div onClick={navBackHandle} className={cn(s.navBack, isMobile && '!mr-0')} />
{!isMobile && (!datasetId ? t('datasetCreation.steps.header.creation') : t('datasetCreation.steps.header.update'))}
</div> </div>
<div className={cn(s.stepList)}>
<div className={cn(s.stepItem, s.step1, step === 1 && s.active, step !== 1 && s.done)}>
<div className={cn(s.stepNum)}>{step === 1 ? 1 : ''}</div>
<div className={cn(s.stepName)}>{t('datasetCreation.steps.one')}</div>
</div>
<div className={cn(s.stepItem, s.step2, step === 2 && s.active, step === 3 && s.done)}>
<div className={cn(s.stepNum)}>{step !== 3 ? 2 : ''}</div>
<div className={cn(s.stepName)}>{t('datasetCreation.steps.two')}</div>
</div>
<div className={cn(s.stepItem, s.step3, step === 3 && s.active)}>
<div className={cn(s.stepNum)}>3</div>
<div className={cn(s.stepName)}>{t('datasetCreation.steps.three')}</div>
</div>
<div className={cn(s.stepList, isMobile && '!p-0')}>
{STEP_LIST.map(item => (
<div
key={item}
className={cn(s.stepItem, s[`step${item}`], step === item && s.active, step > item && s.done, isMobile && 'px-0')}
>
<div className={cn(s.stepNum)}>{item}</div>
<div className={cn(s.stepName)}>{isMobile ? '' : t(STEP_T_MAP[item])}</div>
</div>
))}
</div> </div>
</div> </div>
) )

+ 1
- 1
web/app/components/datasets/documents/detail/completed/index.tsx 查看文件

} }
</div> </div>
<div className={cn(s.footer, s.numberInfo)}> <div className={cn(s.footer, s.numberInfo)}>
<div className='flex items-center'>
<div className='flex items-center flex-wrap gap-y-2'>
<div className={cn(s.commonIcon, s.typeSquareIcon)} /><span className='mr-8'>{formatNumber(segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')}</span> <div className={cn(s.commonIcon, s.typeSquareIcon)} /><span className='mr-8'>{formatNumber(segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')}</span>
<div className={cn(s.commonIcon, s.targetIcon)} /><span className='mr-8'>{formatNumber(segInfo?.hit_count as number)} {t('datasetDocuments.segment.hitCount')}</span> <div className={cn(s.commonIcon, s.targetIcon)} /><span className='mr-8'>{formatNumber(segInfo?.hit_count as number)} {t('datasetDocuments.segment.hitCount')}</span>
<div className={cn(s.commonIcon, s.bezierCurveIcon)} /><span className={s.hashText}>{t('datasetDocuments.segment.vectorHash')}{segInfo?.index_node_hash}</span> <div className={cn(s.commonIcon, s.bezierCurveIcon)} /><span className={s.hashText}>{t('datasetDocuments.segment.vectorHash')}{segInfo?.index_node_hash}</span>

+ 3
- 3
web/app/components/datasets/documents/detail/completed/style.module.css 查看文件

@apply text-gray-900 font-medium text-base flex-1; @apply text-gray-900 font-medium text-base flex-1;
} }
.docSearchWrapper { .docSearchWrapper {
@apply sticky w-full h-10 -top-3 bg-white flex items-center mb-3 justify-between z-10;
@apply sticky w-full py-1 -top-3 bg-white flex items-center mb-3 justify-between z-10 flex-wrap gap-y-1;
} }
.listContainer { .listContainer {
height: calc(100% - 3.25rem); height: calc(100% - 3.25rem);
@apply grid gap-4 grid-cols-3 min-w-[902px] last:mb-[30px]; @apply grid gap-4 grid-cols-3 min-w-[902px] last:mb-[30px];
} }
.segWrapper { .segWrapper {
@apply box-border h-[180px] min-w-[290px] bg-gray-50 px-4 pt-4 flex flex-col text-opacity-50 rounded-xl border border-transparent hover:border-gray-200 hover:shadow-lg hover:cursor-pointer hover:bg-white;
@apply box-border h-[180px] w-full xl:min-w-[290px] bg-gray-50 px-4 pt-4 flex flex-col text-opacity-50 rounded-xl border border-transparent hover:border-gray-200 hover:shadow-lg hover:cursor-pointer hover:bg-white;
} }
.segTitleWrapper { .segTitleWrapper {
@apply flex items-center justify-between; @apply flex items-center justify-between;
white-space: pre-line; white-space: pre-line;
} }
.footer { .footer {
@apply flex items-center justify-between box-border border-t-gray-200 border-t-[0.5px] pt-3 mt-4;
@apply flex items-center justify-between box-border border-t-gray-200 border-t-[0.5px] pt-3 mt-4 flex-wrap gap-y-2;
} }
.numberInfo { .numberInfo {
@apply text-gray-500 text-xs font-medium; @apply text-gray-500 text-xs font-medium;

+ 45
- 35
web/app/components/datasets/documents/detail/index.tsx 查看文件

import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import type { DocForm } from '@/models/datasets' import type { DocForm } from '@/models/datasets'
import { useDatasetDetailContext } from '@/context/dataset-detail' import { useDatasetDetailContext } from '@/context/dataset-detail'
import FloatRightContainer from '@/app/components/base/float-right-container'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


export const DocumentContext = createContext<{ datasetId?: string; documentId?: string; docForm: string }>({ docForm: '' }) export const DocumentContext = createContext<{ datasetId?: string; documentId?: string; docForm: string }>({ docForm: '' })


const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => { const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
const router = useRouter() const router = useRouter()
const { t } = useTranslation() const { t } = useTranslation()

const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const { dataset } = useDatasetDetailContext() const { dataset } = useDatasetDetailContext()
const embeddingAvailable = !!dataset?.embedding_available const embeddingAvailable = !!dataset?.embedding_available
const [showMetadata, setShowMetadata] = useState(true)
const [showMetadata, setShowMetadata] = useState(!isMobile)
const [newSegmentModalVisible, setNewSegmentModalVisible] = useState(false) const [newSegmentModalVisible, setNewSegmentModalVisible] = useState(false)
const [batchModalVisible, setBatchModalVisible] = useState(false) const [batchModalVisible, setBatchModalVisible] = useState(false)
const [importStatus, setImportStatus] = useState<ProcessStatus | string>() const [importStatus, setImportStatus] = useState<ProcessStatus | string>()
return ( return (
<DocumentContext.Provider value={{ datasetId, documentId, docForm: documentDetail?.doc_form || '' }}> <DocumentContext.Provider value={{ datasetId, documentId, docForm: documentDetail?.doc_form || '' }}>
<div className='flex flex-col h-full'> <div className='flex flex-col h-full'>
<div className='flex h-16 border-b-gray-100 border-b items-center p-4'>
<div onClick={backToPrev} className={'rounded-full w-8 h-8 flex justify-center items-center border-gray-100 cursor-pointer border hover:border-gray-300 shadow-[0px_12px_16px_-4px_rgba(16,24,40,0.08),0px_4px_6px_-2px_rgba(16,24,40,0.03)]'}>
<div className='flex min-h-16 border-b-gray-100 border-b items-center p-4 justify-between flex-wrap gap-y-2'>
<div onClick={backToPrev} className={'shrink-0 rounded-full w-8 h-8 flex justify-center items-center border-gray-100 cursor-pointer border hover:border-gray-300 shadow-[0px_12px_16px_-4px_rgba(16,24,40,0.08),0px_4px_6px_-2px_rgba(16,24,40,0.03)]'}>
<ArrowLeftIcon className='text-primary-600 fill-current stroke-current h-4 w-4' /> <ArrowLeftIcon className='text-primary-600 fill-current stroke-current h-4 w-4' />
</div> </div>
<Divider className='!h-4' type='vertical' /> <Divider className='!h-4' type='vertical' />
<DocumentTitle extension={documentDetail?.data_source_info?.upload_file?.extension} name={documentDetail?.name} /> <DocumentTitle extension={documentDetail?.data_source_info?.upload_file?.extension} name={documentDetail?.name} />
<StatusItem status={documentDetail?.display_status || 'available'} scene='detail' errorMessage={documentDetail?.error || ''} />
{embeddingAvailable && documentDetail && !documentDetail.archived && (
<SegmentAdd
importStatus={importStatus}
clearProcessStatus={resetProcessStatus}
showNewSegmentModal={showNewSegmentModal}
showBatchModal={showBatchModal}
<div className='flex items-center flex-wrap gap-y-2'>
<StatusItem status={documentDetail?.display_status || 'available'} scene='detail' errorMessage={documentDetail?.error || ''} />
{embeddingAvailable && documentDetail && !documentDetail.archived && (
<SegmentAdd
importStatus={importStatus}
clearProcessStatus={resetProcessStatus}
showNewSegmentModal={showNewSegmentModal}
showBatchModal={showBatchModal}
/>
)}
<OperationAction
scene='detail'
embeddingAvailable={embeddingAvailable}
detail={{
enabled: documentDetail?.enabled || false,
archived: documentDetail?.archived || false,
id: documentId,
data_source_type: documentDetail?.data_source_type || '',
doc_form: documentDetail?.doc_form || '',
}}
datasetId={datasetId}
onUpdate={handleOperate}
className='!w-[216px]'
/>
<button
className={cn(style.layoutRightIcon, showMetadata ? style.iconShow : style.iconClose)}
onClick={() => setShowMetadata(!showMetadata)}
/> />
)}
<OperationAction
scene='detail'
embeddingAvailable={embeddingAvailable}
detail={{
enabled: documentDetail?.enabled || false,
archived: documentDetail?.archived || false,
id: documentId,
data_source_type: documentDetail?.data_source_type || '',
doc_form: documentDetail?.doc_form || '',
}}
datasetId={datasetId}
onUpdate={handleOperate}
className='!w-[216px]'
/>
<button
className={cn(style.layoutRightIcon, showMetadata ? style.iconShow : style.iconClose)}
onClick={() => setShowMetadata(!showMetadata)}
/>
</div>
</div> </div>
<div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}> <div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}>
{isDetailLoading {isDetailLoading
? <Loading type='app' /> ? <Loading type='app' />
: <div className={`box-border h-full w-full overflow-y-scroll ${embedding ? 'py-12 px-16' : 'pb-[30px] pt-3 px-6'}`}>
: <div className={`h-full w-full flex flex-col ${embedding ? 'px-6 py-3 sm:py-12 sm:px-16' : 'pb-[30px] pt-3 px-6'}`}>
{embedding {embedding
? <Embedding detail={documentDetail} detailUpdate={detailMutate} /> ? <Embedding detail={documentDetail} detailUpdate={detailMutate} />
: <Completed : <Completed
} }
</div> </div>
} }
{showMetadata && <Metadata
docDetail={{ ...documentDetail, ...documentMetadata, doc_type: documentMetadata?.doc_type === 'others' ? '' : documentMetadata?.doc_type } as any}
loading={isMetadataLoading}
onUpdate={metadataMutate}
/>}
<FloatRightContainer showClose isOpen={showMetadata} onClose={() => setShowMetadata(false)} isMobile={isMobile} panelClassname='!justify-start' footer={null}>
<Metadata
docDetail={{ ...documentDetail, ...documentMetadata, doc_type: documentMetadata?.doc_type === 'others' ? '' : documentMetadata?.doc_type } as any}
loading={isMetadataLoading}
onUpdate={metadataMutate}
/>
</FloatRightContainer>
</div> </div>
<BatchModal <BatchModal
isShow={batchModalVisible} isShow={batchModalVisible}

+ 1
- 1
web/app/components/datasets/documents/detail/metadata/style.module.css 查看文件

.main { .main {
@apply w-96 xl:w-[360px] flex-shrink-0 px-6 py-5 overflow-y-auto border-l-gray-100 border-l;
@apply w-full sm:w-96 xl:w-[360px] flex-shrink-0 p-0 sm:px-6 sm:py-5 overflow-y-auto border-none sm:border-l-gray-100 sm:border-l;
} }
.operationWrapper { .operationWrapper {
@apply flex flex-col items-center gap-4 mt-7 mb-8; @apply flex flex-col items-center gap-4 mt-7 mb-8;

+ 2
- 2
web/app/components/datasets/documents/index.tsx 查看文件

<p className={s.desc}>{t('datasetDocuments.list.desc')}</p> <p className={s.desc}>{t('datasetDocuments.list.desc')}</p>
</div> </div>
<div className='flex flex-col px-6 py-4 flex-1'> <div className='flex flex-col px-6 py-4 flex-1'>
<div className='flex items-center justify-between'>
<div className='flex items-center justify-between flex-wrap gap-y-2 '>
<Input <Input
showPrefix showPrefix
wrapperClassName='!w-[200px]' wrapperClassName='!w-[200px]'
value={searchValue} value={searchValue}
/> />
{embeddingAvailable && ( {embeddingAvailable && (
<Button type='primary' onClick={routeToDocCreate} className='!h-8 !text-[13px]'>
<Button type='primary' onClick={routeToDocCreate} className='!h-8 !text-[13px] !shrink-0'>
<PlusIcon className='h-4 w-4 mr-2 stroke-current' /> <PlusIcon className='h-4 w-4 mr-2 stroke-current' />
{isDataSourceNotion && t('datasetDocuments.list.addPages')} {isDataSourceNotion && t('datasetDocuments.list.addPages')}
{!isDataSourceNotion && t('datasetDocuments.list.addFile')} {!isDataSourceNotion && t('datasetDocuments.list.addFile')}

+ 3
- 3
web/app/components/datasets/documents/list.tsx 查看文件

} }


return ( return (
<>
<table className={`w-full border-collapse border-0 text-sm mt-3 ${s.documentTable}`}>
<div className='w-full h-full overflow-x-auto'>
<table className={`min-w-[700px] max-w-full w-full border-collapse border-0 text-sm mt-3 ${s.documentTable}`}>
<thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-medium text-xs uppercase"> <thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-medium text-xs uppercase">
<tr> <tr>
<td className='w-12'>#</td> <td className='w-12'>#</td>
})} })}
</tbody> </tbody>
</table> </table>
</>
</div>
) )
} }



+ 2
- 2
web/app/components/datasets/hit-testing/hit-detail.tsx 查看文件

} }


return ( return (
<div className={'flex flex-row'}>
<div className="flex-1 bg-gray-25 p-6">
<div className='flex flex-row overflow-x-auto'>
<div className="flex-1 bg-gray-25 p-6 min-w-[300px]">
<div className="flex items-center"> <div className="flex items-center">
<SegmentIndexTag <SegmentIndexTag
positionId={segInfo?.position || ''} positionId={segInfo?.position || ''}

+ 60
- 44
web/app/components/datasets/hit-testing/index.tsx 查看文件

'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useMemo, useState } from 'react'
import React, { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useSWR from 'swr' import useSWR from 'swr'
import { omit } from 'lodash-es' import { omit } from 'lodash-es'
import cn from 'classnames' import cn from 'classnames'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useBoolean } from 'ahooks'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import SegmentCard from '../documents/detail/completed/SegmentCard' import SegmentCard from '../documents/detail/completed/SegmentCard'
import docStyle from '../documents/detail/completed/style.module.css' import docStyle from '../documents/detail/completed/style.module.css'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Pagination from '@/app/components/base/pagination' import Pagination from '@/app/components/base/pagination'
import FloatRightContainer from '@/app/components/base/float-right-container'
import { fetchTestingRecords } from '@/service/datasets' import { fetchTestingRecords } from '@/service/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 useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


const limit = 10 const limit = 10




const HitTesting: FC<Props> = ({ datasetId }: Props) => { const HitTesting: FC<Props> = ({ datasetId }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()

const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const [hitResult, setHitResult] = useState<HitTestingResponse | undefined>() // 初始化记录为空数组 const [hitResult, setHitResult] = useState<HitTestingResponse | undefined>() // 初始化记录为空数组
const [submitLoading, setSubmitLoading] = useState(false) const [submitLoading, setSubmitLoading] = useState(false)
const [currParagraph, setCurrParagraph] = useState<{ paraInfo?: HitTestingType; showModal: boolean }>({ showModal: false }) const [currParagraph, setCurrParagraph] = useState<{ paraInfo?: HitTestingType; showModal: boolean }>({ showModal: false })


const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig) const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig)
const [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false) const [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false)
const [isShowRightPanel, { setTrue: showRightPanel, setFalse: hideRightPanel, set: setShowRightPanel }] = useBoolean(!isMobile)

useEffect(() => {
setShowRightPanel(!isMobile)
}, [isMobile, setShowRightPanel])


return ( return (
<div className={s.container}> <div className={s.container}>
<Textarea <Textarea
datasetId={datasetId} datasetId={datasetId}
setHitResult={setHitResult} setHitResult={setHitResult}
onSubmit={showRightPanel}
onUpdateList={recordsMutate} onUpdateList={recordsMutate}
loading={submitLoading} loading={submitLoading}
setLoading={setSubmitLoading} setLoading={setSubmitLoading}
<RecordsEmpty /> <RecordsEmpty />
)} )}
</div> </div>
<div className={s.rightDiv}>
{submitLoading
? <div className={s.cardWrapper}>
<SegmentCard
loading={true}
scene='hitTesting'
className='h-[216px]'
/>
<SegmentCard
loading={true}
scene='hitTesting'
className='h-[216px]'
/>
</div>
: !hitResult?.records.length
? (
<div className='h-full flex flex-col justify-center items-center'>
<div className={cn(docStyle.commonIcon, docStyle.targetIcon, '!bg-gray-200 !h-14 !w-14')} />
<div className='text-gray-300 text-[13px] mt-3'>
{t('datasetHitTesting.hit.emptyTip')}
</div>
</div>
)
: (
<>
<div className='text-gray-600 font-semibold mb-4'>{t('datasetHitTesting.hit.title')}</div>
<div className='overflow-auto flex-1'>
<div className={s.cardWrapper}>
{hitResult?.records.map((record, idx) => {
return <SegmentCard
key={idx}
loading={false}
detail={record.segment as any}
score={record.score}
scene='hitTesting'
className='h-[216px] mb-4'
onClick={() => onClickCard(record as any)}
/>
})}
<FloatRightContainer panelClassname='!justify-start !overflow-y-auto' showClose isMobile={isMobile} isOpen={isShowRightPanel} onClose={hideRightPanel} footer={null}>
<div className={cn(s.rightDiv, 'p-0 sm:px-8 sm:pt-[42px] sm:pb-[26px]')}>
{submitLoading
? <div className={s.cardWrapper}>
<SegmentCard
loading={true}
scene='hitTesting'
className='h-[216px]'
/>
<SegmentCard
loading={true}
scene='hitTesting'
className='h-[216px]'
/>
</div>
: !hitResult?.records.length
? (
<div className='h-full flex flex-col justify-center items-center'>
<div className={cn(docStyle.commonIcon, docStyle.targetIcon, '!bg-gray-200 !h-14 !w-14')} />
<div className='text-gray-300 text-[13px] mt-3'>
{t('datasetHitTesting.hit.emptyTip')}
</div> </div>
</div> </div>
</>
)
}
</div>
)
: (
<>
<div className='text-gray-600 font-semibold mb-4'>{t('datasetHitTesting.hit.title')}</div>
<div className='overflow-auto flex-1'>
<div className={s.cardWrapper}>
{hitResult?.records.map((record, idx) => {
return <SegmentCard
key={idx}
loading={false}
detail={record.segment as any}
score={record.score}
scene='hitTesting'
className='h-[216px] mb-4'
onClick={() => onClickCard(record as any)}
/>
})}
</div>
</div>
</>
)
}
</div>
</FloatRightContainer>
<Modal <Modal
className='!max-w-[960px] !p-0' className='!max-w-[960px] !p-0'
wrapperClassName='!z-40'
closable closable
onClose={() => setCurrParagraph({ showModal: false })} onClose={() => setCurrParagraph({ showModal: false })}
isShow={currParagraph.showModal} isShow={currParagraph.showModal}

+ 2
- 2
web/app/components/datasets/hit-testing/style.module.css 查看文件

.container { .container {
@apply flex h-full w-full relative;
@apply flex h-full w-full relative overflow-y-auto;
} }
.container > div { .container > div {
@apply flex-1 h-full; @apply flex-1 h-full;
@apply border-r border-gray-100 px-6 py-3 flex flex-col; @apply border-r border-gray-100 px-6 py-3 flex flex-col;
} }
.rightDiv { .rightDiv {
@apply px-8 pt-[42px] pb-[26px] flex flex-col;
@apply flex flex-col;
} }
.titleWrapper { .titleWrapper {
@apply flex flex-col justify-center gap-1 mb-5; @apply flex flex-col justify-center gap-1 mb-5;

+ 6
- 4
web/app/components/datasets/hit-testing/textarea.tsx 查看文件

import type { FC } from 'react'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import cn from 'classnames' import cn from 'classnames'
import { asyncRunSafe } from '@/utils' import { asyncRunSafe } from '@/utils'
import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app' import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'


type Props = {
type TextAreaWithButtonIProps = {
datasetId: string datasetId: string
onUpdateList: () => void onUpdateList: () => void
setHitResult: (res: HitTestingResponse) => void setHitResult: (res: HitTestingResponse) => void
onClickRetrievalMethod: () => void onClickRetrievalMethod: () => void
retrievalConfig: RetrievalConfig retrievalConfig: RetrievalConfig
isEconomy: boolean isEconomy: boolean
onSubmit?: () => void
} }


const TextAreaWithButton: FC<Props> = ({
const TextAreaWithButton = ({
datasetId, datasetId,
onUpdateList, onUpdateList,
setHitResult, setHitResult,
onClickRetrievalMethod, onClickRetrievalMethod,
retrievalConfig, retrievalConfig,
isEconomy, isEconomy,
}) => {
onSubmit: _onSubmit,
}: TextAreaWithButtonIProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { indexingTechnique } = useContext(DatasetDetailContext) const { indexingTechnique } = useContext(DatasetDetailContext)


onUpdateList?.() onUpdateList?.()
} }
setLoading(false) setLoading(false)
_onSubmit && _onSubmit()
} }


const retrievalMethod = isEconomy ? RETRIEVE_METHOD.invertedIndex : retrievalConfig.search_method const retrievalMethod = isEconomy ? RETRIEVE_METHOD.invertedIndex : retrievalConfig.search_method

+ 7
- 7
web/app/components/datasets/settings/form/index.tsx 查看文件

import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
const rowClass = ` const rowClass = `
flex justify-between py-4
flex justify-between py-4 flex-wrap gap-y-2
` `
const labelClass = ` const labelClass = `
flex items-center w-[168px] h-9 flex items-center w-[168px] h-9
` `
const inputClass = ` const inputClass = `
w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none
w-full max-w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none
` `
const useInitialValue: <T>(depend: T, dispatch: Dispatch<T>) => void = (depend, dispatch) => { const useInitialValue: <T>(depend: T, dispatch: Dispatch<T>) => void = (depend, dispatch) => {
useEffect(() => { useEffect(() => {
useInitialValue<DataSet['indexing_technique'] | undefined>(currentDataset?.indexing_technique, setIndexMethod) useInitialValue<DataSet['indexing_technique'] | undefined>(currentDataset?.indexing_technique, setIndexMethod)


return ( return (
<div className='w-[800px] px-16 py-6'>
<div className='w-full sm:w-[800px] p-4 sm:px-16 sm:py-6'>
<div className={rowClass}> <div className={rowClass}>
<div className={labelClass}> <div className={labelClass}>
<div>{t('datasetSettings.form.name')}</div> <div>{t('datasetSettings.form.name')}</div>
</div> </div>
<input <input
disabled={!currentDataset?.embedding_available} disabled={!currentDataset?.embedding_available}
className={cn(inputClass, !currentDataset?.embedding_available && 'opacity-60')}
className={cn(inputClass, !currentDataset?.embedding_available && 'opacity-60', 'h-9')}
value={name} value={name}
onChange={e => setName(e.target.value)} onChange={e => setName(e.target.value)}
/> />
<div className={labelClass}> <div className={labelClass}>
<div>{t('datasetSettings.form.desc')}</div> <div>{t('datasetSettings.form.desc')}</div>
</div> </div>
<div>
<div className='w-full max-w-[480px]'>
<textarea <textarea
disabled={!currentDataset?.embedding_available} disabled={!currentDataset?.embedding_available}
className={cn(`${inputClass} block mb-2 h-[120px] py-2 resize-none`, !currentDataset?.embedding_available && 'opacity-60')} className={cn(`${inputClass} block mb-2 h-[120px] py-2 resize-none`, !currentDataset?.embedding_available && 'opacity-60')}
<div className={labelClass}> <div className={labelClass}>
<div>{t('datasetSettings.form.permissions')}</div> <div>{t('datasetSettings.form.permissions')}</div>
</div> </div>
<div className='w-[480px]'>
<div className='w-full sm:w-[480px]'>
<PermissionsRadio <PermissionsRadio
disable={!currentDataset?.embedding_available} disable={!currentDataset?.embedding_available}
value={permission} value={permission}
<div className={labelClass}> <div className={labelClass}>
<div>{t('datasetSettings.form.indexMethod')}</div> <div>{t('datasetSettings.form.indexMethod')}</div>
</div> </div>
<div className='w-[480px]'>
<div className='w-full sm:w-[480px]'>
<IndexMethodRadio <IndexMethodRadio
disable={!currentDataset?.embedding_available} disable={!currentDataset?.embedding_available}
value={indexMethod} value={indexMethod}

+ 2
- 2
web/app/components/datasets/settings/index-method-radio/index.tsx 查看文件

import type { DataSet } from '@/models/datasets' import type { DataSet } from '@/models/datasets'


const itemClass = ` const itemClass = `
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
` `
const radioClass = ` const radioClass = `
w-4 h-4 border-[2px] border-gray-200 rounded-full w-4 h-4 border-[2px] border-gray-200 rounded-full
] ]


return ( return (
<div className={classNames(s.wrapper, 'flex justify-between w-full')}>
<div className={classNames(s.wrapper, 'flex justify-between w-full flex-wrap gap-y-2')}>
{ {
options.map(option => ( options.map(option => (
<div <div

+ 2
- 2
web/app/components/datasets/settings/permissions-radio/index.tsx 查看文件

import type { DataSet } from '@/models/datasets' import type { DataSet } from '@/models/datasets'


const itemClass = ` const itemClass = `
flex items-center w-[234px] h-12 px-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer
flex items-center w-full sm:w-[234px] h-12 px-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer
` `
const radioClass = ` const radioClass = `
w-4 h-4 border-[2px] border-gray-200 rounded-full w-4 h-4 border-[2px] border-gray-200 rounded-full
] ]


return ( return (
<div className={classNames(s.wrapper, 'flex justify-between w-full')}>
<div className={classNames(s.wrapper, 'flex justify-between w-full flex-wrap gap-y-2')}>
{ {
options.map(option => ( options.map(option => (
<div <div

+ 5
- 5
web/app/components/develop/index.tsx 查看文件

'use client' 'use client'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useSWR from 'swr' import useSWR from 'swr'
import s from './secret-key/style.module.css'
import Doc from '@/app/components/develop/doc' import Doc from '@/app/components/develop/doc'
import InputCopy from '@/app/components/develop/secret-key/input-copy' import InputCopy from '@/app/components/develop/secret-key/input-copy'
import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button' import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button'
import { fetchAppDetail } from '@/service/apps' import { fetchAppDetail } from '@/service/apps'
import s from './secret-key/style.module.css'


type IDevelopMainProps = { type IDevelopMainProps = {
appId: string appId: string


return ( return (
<div className='relative flex flex-col h-full overflow-hidden'> <div className='relative flex flex-col h-full overflow-hidden'>
<div className='flex items-center justify-between flex-shrink-0 px-6 border-b border-solid h-14 border-b-gray-100'>
<div className='flex items-center justify-between flex-shrink-0 px-6 border-b border-solid py-2 border-b-gray-100'>
<div className='text-lg font-medium text-gray-900'>{dictionary.app?.develop?.title}</div> <div className='text-lg font-medium text-gray-900'>{dictionary.app?.develop?.title}</div>
<div className='flex items-center'>
<InputCopy className={`flex-shrink-0 mr-1 w-60 ${s.w320}`} value={appDetail?.api_base_url}>
<div className='flex items-center flex-wrap gap-y-1'>
<InputCopy className='flex-shrink-0 mr-1 w-52 sm:w-80' value={appDetail?.api_base_url}>
<div className={`ml-2 border border-gray-200 border-solid flex-shrink-0 px-2 py-0.5 rounded-[6px] text-gray-500 text-[0.625rem] ${s.customApi}`}> <div className={`ml-2 border border-gray-200 border-solid flex-shrink-0 px-2 py-0.5 rounded-[6px] text-gray-500 text-[0.625rem] ${s.customApi}`}>
{t('appApi.apiServer')} {t('appApi.apiServer')}
</div> </div>
<SecretKeyButton className='flex-shrink-0' appId={appId} /> <SecretKeyButton className='flex-shrink-0' appId={appId} />
</div> </div>
</div> </div>
<div className='px-10 py-4 overflow-auto grow'>
<div className='px-4 sm:px-10 py-4 overflow-auto grow'>
<Doc appDetail={appDetail} /> <Doc appDetail={appDetail} />
</div> </div>
</div> </div>

+ 0
- 4
web/app/components/develop/secret-key/style.module.css 查看文件

width: 4rem; width: 4rem;
} }


.w320 {
width: 20rem;
}

.customApi { .customApi {
font-size: 11px; font-size: 11px;
} }

+ 1
- 1
web/app/components/explore/installed-app/index.tsx 查看文件

} }


return ( return (
<div className='h-full p-2'>
<div className='h-full py-2 pl-0 pr-2 sm:p-2'>
{installedApp?.app.mode === 'chat' {installedApp?.app.mode === 'chat'
? ( ? (
<ChatApp isInstalledApp installedAppInfo={installedApp} /> <ChatApp isInstalledApp installedAppInfo={installedApp} />

+ 21
- 16
web/app/components/explore/sidebar/app-nav-item/index.tsx 查看文件

import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'


export type IAppNavItemProps = { export type IAppNavItemProps = {
isMobile: boolean
name: string name: string
id: string id: string
icon: string icon: string
} }


export default function AppNavItem({ export default function AppNavItem({
isMobile,
name, name,
id, id,
icon, icon,
className={cn( className={cn(
s.item, s.item,
isSelected ? s.active : 'hover:bg-gray-200', isSelected ? s.active : 'hover:bg-gray-200',
'flex h-8 items-center justify-between px-2 rounded-lg text-sm font-normal ',
'flex h-8 items-center justify-between mobile:justify-center px-2 mobile:px-1 rounded-lg text-sm font-normal',
)} )}
onClick={() => { onClick={() => {
router.push(url) // use Link causes popup item always trigger jump. Can not be solved by e.stopPropagation(). router.push(url) // use Link causes popup item always trigger jump. Can not be solved by e.stopPropagation().
}} }}
> >
<div className='flex items-center space-x-2 w-0 grow'>
<AppIcon size='tiny' icon={icon} background={icon_background} />
<div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div>
</div>
{
<div className='shrink-0 h-6' onClick={e => e.stopPropagation()}>
<ItemOperation
isPinned={isPinned}
isItemHovering={isHovering}
togglePin={togglePin}
isShowDelete={!uninstallable && !isSelected}
onDelete={() => onDelete(id)}
/>
</div>
}
{isMobile && <AppIcon size='tiny' icon={icon} background={icon_background} />}
{!isMobile && (
<>
<div className='flex items-center space-x-2 w-0 grow'>
<AppIcon size='tiny' icon={icon} background={icon_background} />
<div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div>
</div>
<div className='shrink-0 h-6' onClick={e => e.stopPropagation()}>
<ItemOperation
isPinned={isPinned}
isItemHovering={isHovering}
togglePin={togglePin}
isShowDelete={!uninstallable && !isSelected}
onDelete={() => onDelete(id)}
/>
</div>
</>
)}
</div> </div>
) )
} }

+ 15
- 8
web/app/components/explore/sidebar/index.tsx 查看文件

import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp, updatePinStatus } from '@/service/explore' import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp, updatePinStatus } from '@/service/explore'
import ExploreContext from '@/context/explore-context' import ExploreContext from '@/context/explore-context'
import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


const SelectedDiscoveryIcon = () => ( const SelectedDiscoveryIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
</svg> </svg>
) )


const SideBar: FC<{
export type IExploreSideBarProps = {
controlUpdateInstalledApps: number controlUpdateInstalledApps: number
}> = ({
}

const SideBar: FC<IExploreSideBarProps> = ({
controlUpdateInstalledApps, controlUpdateInstalledApps,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const isChatSelected = lastSegment === 'chat' const isChatSelected = lastSegment === 'chat'
const { installedApps, setInstalledApps } = useContext(ExploreContext) const { installedApps, setInstalledApps } = useContext(ExploreContext)


const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const fetchInstalledAppList = async () => { const fetchInstalledAppList = async () => {
const { installed_apps }: any = await doFetchInstalledAppList() const { installed_apps }: any = await doFetchInstalledAppList()
setInstalledApps(installed_apps) setInstalledApps(installed_apps)
}, [controlUpdateInstalledApps]) }, [controlUpdateInstalledApps])


return ( return (
<div className='w-[216px] shrink-0 pt-6 px-4 border-gray-200 cursor-pointer'>
<div className='w-fit sm:w-[216px] shrink-0 pt-6 px-4 border-gray-200 cursor-pointer'>
<div> <div>
<Link <Link
href='/explore/apps' href='/explore/apps'
className={cn(isDiscoverySelected ? 'text-primary-600 bg-white font-semibold' : 'text-gray-700 font-medium', 'flex items-center h-9 pl-3 space-x-2 rounded-lg')}
className={cn(isDiscoverySelected ? 'text-primary-600 bg-white font-semibold' : 'text-gray-700 font-medium', 'flex items-center mobile:justify-center mobile:w-fit h-9 px-3 mobile:px-2 gap-2 rounded-lg')}
style={isDiscoverySelected ? { boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)' } : {}} style={isDiscoverySelected ? { boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)' } : {}}
> >
{isDiscoverySelected ? <SelectedDiscoveryIcon /> : <DiscoveryIcon />} {isDiscoverySelected ? <SelectedDiscoveryIcon /> : <DiscoveryIcon />}
<div className='text-sm'>{t('explore.sidebar.discovery')}</div>
{!isMobile && <div className='text-sm'>{t('explore.sidebar.discovery')}</div>}
</Link> </Link>
<Link <Link
href='/explore/chat' href='/explore/chat'
className={cn(isChatSelected ? 'text-primary-600 bg-white font-semibold' : 'text-gray-700 font-medium', 'flex items-center h-9 pl-3 space-x-2 rounded-lg')}
className={cn(isChatSelected ? 'text-primary-600 bg-white font-semibold' : 'text-gray-700 font-medium', 'flex items-center mobile:justify-center mobile:w-fit h-9 px-3 mobile:px-2 gap-2 rounded-lg')}
style={isChatSelected ? { boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)' } : {}} style={isChatSelected ? { boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)' } : {}}
> >
{isChatSelected ? <SelectedChatIcon /> : <ChatIcon />} {isChatSelected ? <SelectedChatIcon /> : <ChatIcon />}
<div className='text-sm'>{t('explore.sidebar.chat')}</div>
{!isMobile && <div className='text-sm'>{t('explore.sidebar.chat')}</div>}
</Link> </Link>
</div> </div>
{installedApps.length > 0 && ( {installedApps.length > 0 && (
<div className='mt-10'> <div className='mt-10'>
<div className='pl-2 text-xs text-gray-500 font-medium uppercase'>{t('explore.sidebar.workspace')}</div>
<p className='pl-2 mobile:px-0 text-xs text-gray-500 break-all font-medium uppercase'>{t('explore.sidebar.workspace')}</p>
<div className='mt-3 space-y-1 overflow-y-auto overflow-x-hidden' <div className='mt-3 space-y-1 overflow-y-auto overflow-x-hidden'
style={{ style={{
height: 'calc(100vh - 250px)', height: 'calc(100vh - 250px)',
return ( return (
<Item <Item
key={id} key={id}
isMobile={isMobile}
name={name} name={name}
icon={icon} icon={icon}
icon_background={icon_background} icon_background={icon_background}

+ 3
- 5
web/app/components/explore/universal-chat/index.tsx 查看文件

import { useBoolean, useGetState } from 'ahooks' import { useBoolean, useGetState } from 'ahooks'
import AppUnavailable from '../../base/app-unavailable' import AppUnavailable from '../../base/app-unavailable'
import useConversation from './hooks/use-conversation' import useConversation from './hooks/use-conversation'
import s from './style.module.css'
import Init from './init' import Init from './init'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import Sidebar from '@/app/components/share/chat/sidebar' import Sidebar from '@/app/components/share/chat/sidebar'
return <Loading type='app' /> return <Loading type='app' />


return ( return (
<div className='bg-gray-100'>
<div className='bg-gray-100 h-full'>
<div <div
className={cn( className={cn(
'flex rounded-t-2xl bg-white overflow-hidden rounded-b-2xl',
'flex rounded-t-2xl bg-white overflow-hidden rounded-b-2xl h-full',
)} )}
style={{ style={{
boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)', boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)',
)} )}
{/* main */} {/* main */}
<div className={cn( <div className={cn(
s.installedApp,
'flex-grow flex flex-col overflow-y-auto',
'h-full flex-grow flex flex-col overflow-y-auto',
) )
}> }>
{(!isNewConversation || isResponsing || errorHappened) && ( {(!isNewConversation || isResponsing || errorHappened) && (

+ 5
- 5
web/app/components/explore/universal-chat/init/index.tsx 查看文件

import s from './style.module.css' import s from './style.module.css'


const Line = ( const Line = (
<svg width="720" height="1" viewBox="0 0 720 1" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="100%" height="1" viewBox="0 0 720 1" fill="none" xmlns="http://www.w3.org/2000/svg">
<line y1="0.5" x2="720" y2="0.5" stroke="url(#paint0_linear_6845_53470)"/> <line y1="0.5" x2="720" y2="0.5" stroke="url(#paint0_linear_6845_53470)"/>
<defs> <defs>
<linearGradient id="paint0_linear_6845_53470" x1="0" y1="1" x2="720" y2="1" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_6845_53470" x1="0" y1="1" x2="720" y2="1" gradientUnits="userSpaceOnUse">
const { t } = useTranslation() const { t } = useTranslation()


return ( return (
<div className='h-full flex items-center'>
<div className='h-full flex items-center justify-center'>
<div> <div>
<div className='w-[480px] mx-auto text-center'>
<div className='text-center'>
<div className={cn(s.textGradient, 'mb-2 leading-[32px] font-semibold text-[24px]')}>{t('explore.universalChat.welcome')}</div> <div className={cn(s.textGradient, 'mb-2 leading-[32px] font-semibold text-[24px]')}>{t('explore.universalChat.welcome')}</div>
<div className='mb-2 font-normal text-sm text-gray-500'>{t('explore.universalChat.welcomeDescribe')}</div> <div className='mb-2 font-normal text-sm text-gray-500'>{t('explore.universalChat.welcomeDescribe')}</div>
</div> </div>
<div className='flex mb-2 mx-auto h-8 items-center'>
<div className='flex mb-2 h-8 items-center'>
{Line} {Line}
</div> </div>
<Config className='w-[480px] mx-auto' {...configProps} />
<Config {...configProps} />
</div> </div>
</div> </div>
) )

+ 0
- 3
web/app/components/explore/universal-chat/style.module.css 查看文件

.installedApp {
height: calc(100vh - 74px);
}

+ 2
- 9
web/app/components/header/HeaderWrapper.tsx 查看文件

import classNames from 'classnames' import classNames from 'classnames'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import s from './index.module.css' import s from './index.module.css'
import { useAppContext } from '@/context/app-context'


type HeaderWrapperProps = { type HeaderWrapperProps = {
children: React.ReactNode children: React.ReactNode
children, children,
}: HeaderWrapperProps) => { }: HeaderWrapperProps) => {
const pathname = usePathname() const pathname = usePathname()
const { langeniusVersionInfo } = useAppContext()
const isBordered = ['/apps', '/datasets'].includes(pathname) const isBordered = ['/apps', '/datasets'].includes(pathname)


return ( return (
<div className={classNames( <div className={classNames(
'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14',
'sticky top-0 left-0 right-0 z-20 flex flex-col bg-gray-100 grow-0 shrink-0 basis-auto min-h-[56px]',
s.header, s.header,
isBordered ? 'border-b border-gray-200' : '', isBordered ? 'border-b border-gray-200' : '',
)} )}
> >
<div className={classNames(
s[`header-${langeniusVersionInfo.current_env}`],
'flex flex-1 items-center justify-between px-4',
)}>
{children}
</div>
{children}
</div> </div>
) )
} }

+ 11
- 4
web/app/components/header/account-dropdown/index.tsx 查看文件

import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general' import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'


export default function AppSelector() {
export type IAppSelecotr = {
isMobile: boolean
}

export default function AppSelector({ isMobile }: IAppSelecotr) {
const itemClassName = ` const itemClassName = `
flex items-center w-full h-9 px-3 text-gray-700 text-[14px] flex items-center w-full h-9 px-3 text-gray-700 text-[14px]
rounded-lg font-normal hover:bg-gray-50 cursor-pointer rounded-lg font-normal hover:bg-gray-50 cursor-pointer
inline-flex items-center inline-flex items-center
rounded-[20px] py-1 pr-2.5 pl-1 text-sm rounded-[20px] py-1 pr-2.5 pl-1 text-sm
text-gray-700 hover:bg-gray-200 text-gray-700 hover:bg-gray-200
mobile:px-1
${open && 'bg-gray-200'} ${open && 'bg-gray-200'}
`} `}
> >
<Avatar name={userProfile.name} className='mr-2' size={32} />
{userProfile.name}
<ChevronDown className="w-3 h-3 ml-1 text-gray-700"/>
<Avatar name={userProfile.name} className='sm:mr-2 mr-0' size={32} />
{!isMobile && <>
{userProfile.name}
<ChevronDown className="w-3 h-3 ml-1 text-gray-700"/>
</>}
</Menu.Button> </Menu.Button>
</div> </div>
<Transition <Transition

+ 1
- 1
web/app/components/header/account-setting/api-based-extension-page/selector.tsx 查看文件

) )
} }
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='w-[576px] z-[11]'>
<PortalToFollowElemContent className='w-[calc(100%-32px)] max-w-[576px] z-[11]'>
<div className='w-full rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg z-10'> <div className='w-full rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg z-10'>
<div className='p-1'> <div className='p-1'>
<div className='flex items-center justify-between px-3 pt-2 pb-1'> <div className='flex items-center justify-between px-3 pt-2 pb-1'>

+ 1
- 1
web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx 查看文件

: ( : (
<div <div
className={ className={
`flex items-center px-3 h-7 bg-white border-[0.5px] border-gray-200 text-xs font-medium text-primary-600 rounded-md
`flex items-center px-3 py-1 min-h-7 bg-white border-[0.5px] border-gray-200 text-xs font-medium text-primary-600 rounded-md
${isCurrentWorkspaceManager ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}` ${isCurrentWorkspaceManager ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
} }
onClick={handleConnectNotion} onClick={handleConnectNotion}

+ 10
- 5
web/app/components/header/account-setting/index.tsx 查看文件

import { Globe01 } from '@/app/components/base/icons/src/vender/line/mapsAndTravel' import { Globe01 } from '@/app/components/base/icons/src/vender/line/mapsAndTravel'
import { AtSign, XClose } from '@/app/components/base/icons/src/vender/line/general' import { AtSign, XClose } from '@/app/components/base/icons/src/vender/line/general'
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes' import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


const iconClassName = ` const iconClassName = `
w-4 h-4 ml-3 mr-2 w-4 h-4 ml-3 mr-2
}: IAccountSettingProps) { }: IAccountSettingProps) {
const [activeMenu, setActiveMenu] = useState(activeTab) const [activeMenu, setActiveMenu] = useState(activeTab)
const { t } = useTranslation() const { t } = useTranslation()

const media = useBreakpoints()
const isMobile = media === MediaType.mobile

const menuItems = [ const menuItems = [
{ {
key: 'workspace-group', key: 'workspace-group',
wrapperClassName='!z-20 pt-[60px]' wrapperClassName='!z-20 pt-[60px]'
> >
<div className='flex'> <div className='flex'>
<div className='w-[200px] p-4 border border-gray-100'>
<div className='mb-8 ml-2 text-base font-medium leading-6 text-gray-900'>{t('common.userProfile.settings')}</div>
<div>
<div className='w-[44px] sm:w-[200px] px-[1px] py-4 sm:p-4 border border-gray-100 shrink-0 sm:shrink-1 flex flex-col items-center sm:items-start'>
<div className='mb-8 ml-0 sm:ml-2 text-sm sm:text-base font-medium leading-6 text-gray-900'>{t('common.userProfile.settings')}</div>
<div className='w-full'>
{ {
menuItems.map(menuItem => ( menuItems.map(menuItem => (
<div key={menuItem.key} className='mb-4'> <div key={menuItem.key} className='mb-4'>
onClick={() => setActiveMenu(item.key)} onClick={() => setActiveMenu(item.key)}
> >
{activeMenu === item.key ? item.activeIcon : item.icon} {activeMenu === item.key ? item.activeIcon : item.icon}
<div className='truncate'>{item.name}</div>
{!isMobile && <div className='truncate'>{item.name}</div>}
</div> </div>
)) ))
} }
<XClose className='w-4 h-4 text-gray-500' /> <XClose className='w-4 h-4 text-gray-500' />
</div> </div>
</div> </div>
<div className='px-8 pt-2'>
<div className='px-4 sm:px-8 pt-2'>
{activeMenu === 'account' && <AccountPage />} {activeMenu === 'account' && <AccountPage />}
{activeMenu === 'members' && <MembersPage />} {activeMenu === 'members' && <MembersPage />}
{activeMenu === 'integrations' && <IntegrationsPage />} {activeMenu === 'integrations' && <IntegrationsPage />}

+ 3
- 3
web/app/components/header/account-setting/members-page/index.tsx 查看文件

{t('common.members.invite')} {t('common.members.invite')}
</div> </div>
</div> </div>
<div>
<div className='flex items-center py-[7px] border-b border-gray-200'>
<div className='overflow-x-auto'>
<div className='flex items-center py-[7px] border-b border-gray-200 min-w-[480px]'>
<div className='grow px-3 text-xs font-medium text-gray-500'>{t('common.members.name')}</div> <div className='grow px-3 text-xs font-medium text-gray-500'>{t('common.members.name')}</div>
<div className='shrink-0 w-[104px] text-xs font-medium text-gray-500'>{t('common.members.lastActive')}</div> <div className='shrink-0 w-[104px] text-xs font-medium text-gray-500'>{t('common.members.lastActive')}</div>
<div className='shrink-0 w-[96px] px-3 text-xs font-medium text-gray-500'>{t('common.members.role')}</div> <div className='shrink-0 w-[96px] px-3 text-xs font-medium text-gray-500'>{t('common.members.role')}</div>
</div> </div>
<div>
<div className='min-w-[480px]'>
{ {
accounts.map(account => ( accounts.map(account => (
<div key={account.id} className='flex border-b border-gray-100'> <div key={account.id} className='flex border-b border-gray-100'>

+ 1
- 1
web/app/components/header/account-setting/model-page/index.tsx 查看文件

} }
<SystemModel onUpdate={() => mutateProviders()} /> <SystemModel onUpdate={() => mutateProviders()} />
</div> </div>
<div className='grid grid-cols-2 gap-4 mb-6'>
<div className='grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6'>
{ {
MODEL_CARD_LIST.map((model, index) => ( MODEL_CARD_LIST.map((model, index) => (
<ModelCard <ModelCard

+ 1
- 1
web/app/components/header/account-setting/model-page/model-item/index.tsx 查看文件



return ( return (
<div className='mb-2 bg-gray-50 rounded-xl'> <div className='mb-2 bg-gray-50 rounded-xl'>
<div className='flex justify-between items-center px-4 h-14'>
<div className='flex justify-between items-center p-4 min-h-[56px] flex-wrap gap-y-1'>
<div className='flex items-center'> <div className='flex items-center'>
{modelItem.titleIcon[locale]} {modelItem.titleIcon[locale]}
{ {

+ 1
- 1
web/app/components/header/account-setting/model-page/model-modal/Form.tsx 查看文件

options?.map(option => ( options?.map(option => (
<div <div
className={` className={`
flex items-center px-3 h-9 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
${value?.[field.key] === option.key && 'bg-white border-[1.5px] border-primary-400 shadow-sm'} ${value?.[field.key] === option.key && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
`} `}
onClick={() => handleFormChange(field.key, option.key)} onClick={() => handleFormChange(field.key, option.key)}

+ 2
- 2
web/app/components/header/account-setting/model-page/model-modal/index.tsx 查看文件

<PortalToFollowElem open> <PortalToFollowElem open>
<PortalToFollowElemContent className='w-full h-full z-[60]'> <PortalToFollowElemContent className='w-full h-full z-[60]'>
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'> <div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
<div className='w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'>
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'>
<div className='px-8 pt-8'> <div className='px-8 pt-8'>
<div className='flex justify-between items-center mb-2'> <div className='flex justify-between items-center mb-2'>
<div className='text-xl font-semibold text-gray-900'>{renderTitlePrefix()}</div> <div className='text-xl font-semibold text-gray-900'>{renderTitlePrefix()}</div>
onClearedChange={setCleared} onClearedChange={setCleared}
onValidating={handleValidating} onValidating={handleValidating}
/> />
<div className='flex justify-between items-center py-6'>
<div className='flex justify-between items-center py-6 flex-wrap gap-y-2'>
<a <a
href={modelModal?.link.href} href={modelModal?.link.href}
target='_blank' target='_blank'

+ 65
- 18
web/app/components/header/index.tsx 查看文件

'use client' 'use client'


import Link from 'next/link' import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'
import classNames from 'classnames'
import { useEffect } from 'react'
import { Bars3Icon } from '@heroicons/react/20/solid'
import { useBoolean } from 'ahooks'
import AccountDropdown from './account-dropdown' import AccountDropdown from './account-dropdown'
import AppNav from './app-nav' import AppNav from './app-nav'
import DatasetNav from './dataset-nav' import DatasetNav from './dataset-nav'
import EnvNav from './env-nav' import EnvNav from './env-nav'
import ExploreNav from './explore-nav' import ExploreNav from './explore-nav'
import GithubStar from './github-star' import GithubStar from './github-star'
import s from './index.module.css'
import { WorkspaceProvider } from '@/context/workspace-context' import { WorkspaceProvider } from '@/context/workspace-context'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import LogoSite from '@/app/components/base/logo/logo-site' import LogoSite from '@/app/components/base/logo/logo-site'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'


const navClassName = ` const navClassName = `
flex items-center relative mr-3 px-3 h-8 rounded-xl
flex items-center relative mr-3 px-3 h-9 rounded-xl
font-medium text-sm font-medium text-sm
cursor-pointer cursor-pointer
` `


const Header = () => { const Header = () => {
const { isCurrentWorkspaceManager } = useAppContext()
const selectedSegment = useSelectedLayoutSegment()
const { isCurrentWorkspaceManager, langeniusVersionInfo } = useAppContext()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false)

useEffect(() => {
hideNavMenu()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedSegment])

return ( return (
<> <>
<div className='flex items-center'>
<Link href="/apps" className='flex items-center mr-4'>
<LogoSite />
</Link>
<GithubStar />
</div>
<div className='flex items-center'>
<ExploreNav className={navClassName} />
<AppNav />
{isCurrentWorkspaceManager && <DatasetNav />}
</div>
<div className='flex items-center flex-shrink-0'>
<EnvNav />
<WorkspaceProvider>
<AccountDropdown />
</WorkspaceProvider>
<div className={classNames(
s[`header-${langeniusVersionInfo.current_env}`],
'flex flex-1 items-center justify-between px-4',
)}>
<div className='flex items-center'>
{isMobile && <div
className='flex items-center justify-center h-8 w-8 cursor-pointer'
onClick={toggle}
>
<Bars3Icon className="h-4 w-4 text-gray-500" />
</div>}
{!isMobile && <>
<Link href="/apps" className='flex items-center mr-4'>
<LogoSite />
</Link>
<GithubStar />
</>}
</div>
{isMobile && (
<div className='flex'>
<Link href="/apps" className='flex items-center mr-4'>
<LogoSite />
</Link>
<GithubStar />
</div>
)}
{!isMobile && (
<div className='flex items-center'>
<ExploreNav className={navClassName} />
<AppNav />
{isCurrentWorkspaceManager && <DatasetNav />}
</div>
)}
<div className='flex items-center flex-shrink-0'>
<EnvNav />
<WorkspaceProvider>
<AccountDropdown isMobile={isMobile} />
</WorkspaceProvider>
</div>
</div> </div>
{(isMobile && isShowNavMenu) && (
<div className='w-full flex flex-col p-2 gap-y-1'>
<ExploreNav className={navClassName} />
<AppNav />
{isCurrentWorkspaceManager && <DatasetNav />}
</div>
)}
</> </>
) )
} }

+ 1
- 1
web/app/components/header/nav/index.tsx 查看文件



return ( return (
<div className={` <div className={`
flex items-center h-8 mr-3 px-0.5 rounded-xl text-sm shrink-0 font-medium
flex items-center h-8 mr-0 sm:mr-3 px-0.5 rounded-xl text-sm shrink-0 font-medium
${isActived && 'bg-white shadow-md font-semibold'} ${isActived && 'bg-white shadow-md font-semibold'}
${!curNav && !isActived && 'hover:bg-gray-200'} ${!curNav && !isActived && 'hover:bg-gray-200'}
`}> `}>

+ 2
- 3
web/app/components/share/chat/index.tsx 查看文件

import AppUnavailable from '../../base/app-unavailable' import AppUnavailable from '../../base/app-unavailable'
import { checkOrSetAccessToken } from '../utils' import { checkOrSetAccessToken } from '../utils'
import useConversation from './hooks/use-conversation' import useConversation from './hooks/use-conversation'
import s from './style.module.css'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import Sidebar from '@/app/components/share/chat/sidebar' import Sidebar from '@/app/components/share/chat/sidebar'
import ConfigSence from '@/app/components/share/chat/config-scence' import ConfigSence from '@/app/components/share/chat/config-scence'
} }


return ( return (
<div className='bg-gray-100'>
<div className='bg-gray-100 h-full'>
{!isInstalledApp && ( {!isInstalledApp && (
<Header <Header
title={siteInfo.title} title={siteInfo.title}
)} )}
{/* main */} {/* main */}
<div className={cn( <div className={cn(
isInstalledApp ? s.installedApp : 'h-[calc(100vh_-_3rem)] tablet:h-screen',
isInstalledApp ? 'h-full' : 'h-[calc(100vh_-_3rem)] tablet:h-screen',
'flex-grow flex flex-col overflow-y-auto', 'flex-grow flex flex-col overflow-y-auto',
) )
}> }>

+ 0
- 3
web/app/components/share/chat/style.module.css 查看文件

.installedApp {
height: calc(100vh - 74px);
}

+ 1
- 2
web/app/components/share/chatbot/index.tsx 查看文件

import { checkOrSetAccessToken } from '../utils' import { checkOrSetAccessToken } from '../utils'
import AppUnavailable from '../../base/app-unavailable' import AppUnavailable from '../../base/app-unavailable'
import useConversation from './hooks/use-conversation' import useConversation from './hooks/use-conversation'
import s from './style.module.css'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import ConfigScene from '@/app/components/share/chatbot/config-scence' import ConfigScene from '@/app/components/share/chatbot/config-scence'
import Header from '@/app/components/share/header' import Header from '@/app/components/share/header'


<div className={'flex bg-white overflow-hidden'}> <div className={'flex bg-white overflow-hidden'}>
<div className={cn( <div className={cn(
isInstalledApp ? s.installedApp : 'h-[calc(100vh_-_3rem)]',
isInstalledApp ? 'h-full' : 'h-[calc(100vh_-_3rem)]',
'flex-grow flex flex-col overflow-y-auto', 'flex-grow flex flex-col overflow-y-auto',
) )
}> }>

+ 0
- 3
web/app/components/share/chatbot/style.module.css 查看文件

.installedApp {
height: calc(100vh - 74px);
}

+ 2
- 2
web/app/components/share/header.tsx 查看文件

return ( return (
<div <div
className={` className={`
shrink-0 flex items-center justify-between h-12 px-3 bg-gray-100
shrink-0 flex items-center justify-between h-14 px-4 bg-gray-100
bg-gradient-to-r from-blue-600 to-sky-500 bg-gradient-to-r from-blue-600 to-sky-500
`} `}
> >
} }


return ( return (
<div className="shrink-0 flex items-center justify-between h-12 px-3 bg-gray-100">
<div className="shrink-0 flex items-center justify-between h-14 px-4 bg-gray-100">
<div <div
className='flex items-center justify-center h-8 w-8 cursor-pointer' className='flex items-center justify-center h-8 w-8 cursor-pointer'
onClick={() => onShowSideBar?.()} onClick={() => onShowSideBar?.()}

+ 1
- 1
web/app/components/share/text-generation/style.module.css 查看文件

.installedApp { .installedApp {
height: calc(100vh - 74px);
height: 100%;
border-radius: 16px; border-radius: 16px;
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
} }

+ 2
- 2
web/context/app-context.tsx 查看文件

isCurrentWorkspaceManager, isCurrentWorkspaceManager,
mutateCurrentWorkspace, mutateCurrentWorkspace,
}}> }}>
<div className='flex flex-col h-full'>
<div className='flex flex-col h-full overflow-y-auto'>
{globalThis.document?.body?.getAttribute('data-public-maintenance-notice') && <MaintenanceNotice />} {globalThis.document?.body?.getAttribute('data-public-maintenance-notice') && <MaintenanceNotice />}
<div ref={pageContainerRef} className='grow relative flex flex-col overflow-auto bg-gray-100'>
<div ref={pageContainerRef} className='grow relative flex flex-col overflow-y-auto overflow-x-hidden bg-gray-100'>
{children} {children}
</div> </div>
</div> </div>

+ 1
- 0
web/i18n/lang/app-debug.en.ts 查看文件

operation: { operation: {
applyConfig: 'Publish', applyConfig: 'Publish',
resetConfig: 'Reset', resetConfig: 'Reset',
debugConfig: 'Debug',
addFeature: 'Add Feature', addFeature: 'Add Feature',
automatic: 'Automatic', automatic: 'Automatic',
stopResponding: 'Stop responding', stopResponding: 'Stop responding',

+ 1
- 0
web/i18n/lang/app-debug.zh.ts 查看文件

operation: { operation: {
applyConfig: '发布', applyConfig: '发布',
resetConfig: '重置', resetConfig: '重置',
debugConfig: '调试',
addFeature: '添加功能', addFeature: '添加功能',
automatic: '自动编排', automatic: '自动编排',
stopResponding: '停止响应', stopResponding: '停止响应',

+ 1
- 0
web/i18n/lang/dataset-creation.en.ts 查看文件

sideTipP3: 'Cleaning removes unnecessary characters and formats, making datasets cleaner and easier to parse.', sideTipP3: 'Cleaning removes unnecessary characters and formats, making datasets cleaner and easier to parse.',
sideTipP4: 'Proper segmentation and cleaning improve model performance, providing more accurate and valuable results.', sideTipP4: 'Proper segmentation and cleaning improve model performance, providing more accurate and valuable results.',
previewTitle: 'Preview', previewTitle: 'Preview',
previewTitleButton: 'Preview',
previewButton: 'Switching to Q&A format', previewButton: 'Switching to Q&A format',
previewSwitchTipStart: 'The current segment preview is in text format, switching to a question-and-answer format preview will', previewSwitchTipStart: 'The current segment preview is in text format, switching to a question-and-answer format preview will',
previewSwitchTipEnd: ' consume additional tokens', previewSwitchTipEnd: ' consume additional tokens',

+ 1
- 0
web/i18n/lang/dataset-creation.zh.ts 查看文件

sideTipP3: '清洗则是对文本进行预处理,删除不必要的字符、符号或格式,使数据集更加干净、整洁,便于模型解析。', sideTipP3: '清洗则是对文本进行预处理,删除不必要的字符、符号或格式,使数据集更加干净、整洁,便于模型解析。',
sideTipP4: '通过对数据集进行适当的分段和清洗,可以提高模型在实际应用中的表现,从而为用户提供更准确、更有价值的结果。', sideTipP4: '通过对数据集进行适当的分段和清洗,可以提高模型在实际应用中的表现,从而为用户提供更准确、更有价值的结果。',
previewTitle: '分段预览', previewTitle: '分段预览',
previewTitleButton: '预览',
previewButton: '切换至 Q&A 形式', previewButton: '切换至 Q&A 形式',
previewSwitchTipStart: '当前分段预览是文本模式,切换到 Q&A 模式将会', previewSwitchTipStart: '当前分段预览是文本模式,切换到 Q&A 模式将会',
previewSwitchTipEnd: '消耗额外的 token', previewSwitchTipEnd: '消耗额外的 token',

Loading…
取消
儲存