feat: fe mobile responsive next (#1609)

This commit is contained in:
Yuhao 2023-11-27 11:47:48 +08:00 committed by GitHub
parent 3cc697832a
commit a9c1c7d239
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 768 additions and 485 deletions

View File

@ -39,10 +39,10 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
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])
@ -56,7 +56,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
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>
) )
} }

View File

@ -93,7 +93,7 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
} }
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"

View File

@ -19,7 +19,7 @@ const Overview = async ({
*/ */
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')}

View File

@ -122,7 +122,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
<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 && (
@ -139,7 +139,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
</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}
@ -161,7 +161,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
) )
: ( : (
<> <>
<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')}

View File

@ -4,6 +4,8 @@ import React, { useEffect } from 'react'
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,
@ -11,6 +13,8 @@ import {
// 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,
@ -20,29 +24,39 @@ import {
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`}> <Link className={classNames(s.itemWrapper, 'px-0 sm:px-3 justify-center sm:justify-start')} href={`/app/${detail?.id}/overview`}>
<div className={s.iconWrapper}> <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}>
@ -50,7 +64,7 @@ const LikedItem: FC<{ type?: 'plugin' | 'app'; appStatus?: boolean; detail: Rela
</div> </div>
)} )}
</div> </div>
<div className={s.appInfo}>{detail?.name || '--'}</div> {!isMobile && <div className={s.appInfo}>{detail?.name || '--'}</div>}
</Link> </Link>
) )
} }
@ -83,6 +97,68 @@ const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
</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,
@ -91,6 +167,10 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
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,
@ -113,54 +193,18 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
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={{
@ -168,7 +212,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
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>
) )

View File

@ -14,14 +14,12 @@ const Settings = async ({
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} />
<Form datasetId={datasetId} />
</div>
</div> </div>
) )
} }

View File

@ -1,11 +1,11 @@
.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;

View File

@ -15,10 +15,10 @@ const ApiServer: FC<ApiServerProps> = ({
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<div className='flex items-center'> <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'> <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'>{t('appApi.apiServer')}</div> <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 w-[248px] text-[13px] font-medium text-gray-800'>{apiBaseUrl}</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}

View File

@ -29,7 +29,7 @@ const Container = () => {
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)}
@ -38,16 +38,14 @@ const Container = () => {
{activeTab === 'api' && data && <ApiServer apiBaseUrl={data.api_base_url || ''} />} {activeTab === 'api' && data && <ApiServer apiBaseUrl={data.api_base_url || ''} />}
</div> </div>
{activeTab === 'dataset' {activeTab === 'dataset' && (
? ( <>
<> <Datasets containerRef={containerRef} />
<Datasets containerRef={containerRef} /> <DatasetFooter />
<DatasetFooter /> </>
</> )}
)
: ( {activeTab === 'api' && data && <Doc apiBaseUrl={data.api_base_url || ''} />}
activeTab === 'api' && data && <Doc apiBaseUrl={data.api_base_url || ''} />
)}
</div> </div>
) )

View File

@ -15,7 +15,7 @@ const Doc: FC<DocProps> = ({
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} />

View File

@ -15,6 +15,7 @@ export type IAppBasicProps = {
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">
@ -31,18 +32,18 @@ const DatasetSvg = <svg width="20" height="20" viewBox="0 0 20 20" fill="none" x
</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 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 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="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>
@ -55,7 +56,7 @@ const ICON_MAP = {
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' && (
@ -69,7 +70,7 @@ export default function AppBasic({ icon, icon_background, name, type, hoverTip,
</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
@ -78,7 +79,7 @@ export default function AppBasic({ icon, icon_background, name, type, 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>
) )
} }

View File

@ -1,8 +1,8 @@
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'
@ -20,15 +20,19 @@ export type IAppDetailNavProps = {
} }
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}

View File

@ -18,12 +18,14 @@ export type NavLinkProps = {
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()
@ -45,7 +47,7 @@ export default function NavLink({
)} )}
aria-hidden="true" aria-hidden="true"
/> />
{name} {mode === 'expand' && name}
</Link> </Link>
) )
} }

View File

@ -27,6 +27,7 @@ import Loading from '@/app/components/base/loading'
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
@ -54,6 +55,10 @@ const ConfigModel: FC<IConfigModelProps> = ({
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]
@ -288,7 +293,7 @@ const ConfigModel: FC<IConfigModelProps> = ({
</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">
@ -340,7 +345,7 @@ const ConfigModel: FC<IConfigModelProps> = ({
<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]',
@ -351,7 +356,7 @@ const ConfigModel: FC<IConfigModelProps> = ({
> >
<> <>
{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>
@ -361,12 +366,12 @@ const ConfigModel: FC<IConfigModelProps> = ({
</> </>
<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>

View File

@ -20,7 +20,7 @@ const ModelModeTypeLabel: FC<Props> = ({
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>

View File

@ -18,7 +18,7 @@ const ModelName: FC<IModelNameProps> = ({
modelDisplayName, modelDisplayName,
}) => { }) => {
return ( return (
<span title={modelDisplayName}> <span className='text-ellipsis overflow-hidden whitespace-nowrap' title={modelDisplayName}>
{modelDisplayName} {modelDisplayName}
</span> </span>
) )

View File

@ -49,7 +49,7 @@ const ParamItem: FC<IParamIteProps> = ({ id, name, tip, step = 0.1, min = 0, max
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>

View File

@ -185,8 +185,8 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
<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'> <div className='rounded-lg border border-gray-200 bg-white overflow-x-auto'>
<table className={`${s.table} w-full border-collapse border-0 rounded-lg text-sm`}> <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>

View File

@ -31,7 +31,7 @@ const ParamsConfig: FC = () => {
</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>

View File

@ -16,6 +16,7 @@ import Loading from '@/app/components/base/loading'
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">
@ -47,6 +48,9 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
}) => { }) => {
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 = () => {
@ -103,15 +107,36 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
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='flex h-[680px] flex-wrap gap-y-4 overflow-y-auto'>
<div className='w-[480px] shrink-0 px-8 py-6 h-full overflow-y-auto border-r border-gray-100'> {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>
@ -139,7 +164,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
</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'>
@ -180,7 +205,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
</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')}

View File

@ -9,6 +9,8 @@ import { formatNumber } from '@/utils/format'
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
@ -24,6 +26,10 @@ const Item: FC<ItemProps> = ({
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) => {
@ -74,15 +80,13 @@ const Item: FC<ItemProps> = ({
<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>
{ <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'>
showSettingsModal && ( <SettingsModal
<SettingsModal currentDataset={config}
currentDataset={config} onCancel={() => setShowSettingsModal(false)}
onCancel={() => setShowSettingsModal(false)} onSave={handleSave}
onSave={handleSave} />
/> </Drawer>
)
}
</div> </div>
) )
} }

View File

@ -137,7 +137,7 @@ const ParamsConfig: FC = () => {
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')}
> >

View File

@ -1,6 +1,5 @@
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'
@ -30,7 +29,7 @@ type SettingsModalProps = {
} }
const rowClass = ` const rowClass = `
flex justify-between py-4 flex justify-between py-4 flex-wrap gap-y-2
` `
const labelClass = ` const labelClass = `
@ -45,10 +44,6 @@ const SettingsModal: FC<SettingsModalProps> = ({
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)
@ -122,10 +117,8 @@ const SettingsModal: FC<SettingsModalProps> = ({
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}
@ -179,12 +172,12 @@ const SettingsModal: FC<SettingsModalProps> = ({
<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>
@ -198,7 +191,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
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>
@ -272,7 +265,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
)} )}
<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)',
}} }}

View File

@ -8,6 +8,7 @@ import produce from 'immer'
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'
@ -44,6 +45,8 @@ import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/
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
@ -64,6 +67,10 @@ const Configuration: FC = () => {
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>({
@ -600,7 +607,7 @@ const Configuration: FC = () => {
> >
<> <>
<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'>
@ -630,7 +637,7 @@ const Configuration: FC = () => {
</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}
@ -644,22 +651,28 @@ const Configuration: FC = () => {
}} }}
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 && (
@ -707,6 +720,15 @@ const Configuration: FC = () => {
}} }}
/> />
)} )}
{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>
) )

View File

@ -39,7 +39,7 @@ const Filter: FC<IFilterProps> = ({ appId, queryParams, setQueryParams }: IFilte
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'
@ -47,7 +47,7 @@ const Filter: FC<IFilterProps> = ({ appId, queryParams, setQueryParams }: IFilte
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]'

View File

@ -33,6 +33,7 @@ import { TONE_LIST } from '@/config'
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
@ -200,7 +201,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
<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]')}
> >
@ -412,6 +413,10 @@ const ChatConversationDetailComp: FC<{ appId?: string; conversationId?: string }
*/ */
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
@ -445,17 +450,17 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
return <Loading /> return <Loading />
return ( return (
<> <div className='overflow-x-auto'>
<table className={`w-full border-collapse border-0 text-sm mt-3 ${s.logTable}`}> <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"> <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 className='w-[1.375rem] whitespace-nowrap'></td>
<td>{t('appLog.table.header.time')}</td> <td className='whitespace-nowrap'>{t('appLog.table.header.time')}</td>
<td>{t('appLog.table.header.endUser')}</td> <td className='whitespace-nowrap'>{t('appLog.table.header.endUser')}</td>
<td>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td> <td className='whitespace-nowrap'>{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 className='whitespace-nowrap'>{isChatMode ? t('appLog.table.header.messageCount') : t('appLog.table.header.output')}</td>
<td>{t('appLog.table.header.userRate')}</td> <td className='whitespace-nowrap'>{t('appLog.table.header.userRate')}</td>
<td>{t('appLog.table.header.adminRate')}</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">
@ -504,9 +509,9 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
<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,
@ -518,7 +523,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
} }
</DrawerContext.Provider> </DrawerContext.Provider>
</Drawer> </Drawer>
</> </div>
) )
} }

View File

@ -129,7 +129,7 @@ function AppCard({
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 ?? ''
}`} }`}
> >
@ -163,8 +163,8 @@ function AppCard({
: 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="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"> <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>
@ -196,7 +196,7 @@ function AppCard({
</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

View File

@ -81,10 +81,10 @@ const CustomizeModal: FC<IShareLinkProps> = ({
</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}'`}

View File

@ -106,7 +106,7 @@ const Embedded = ({ isShow, onClose, appBaseUrl, accessToken }: Props) => {
<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
@ -150,7 +150,7 @@ const Embedded = ({ isShow, onClose, appBaseUrl, accessToken }: Props) => {
</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>

View File

@ -1,9 +1,10 @@
'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
@ -12,6 +13,7 @@ type DrawerProps = {
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
@ -24,11 +26,12 @@ export default function Drawer({
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
@ -52,6 +55,9 @@ export default function Drawer({
> >
{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}
</> </>

View File

@ -0,0 +1,37 @@
'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

View File

@ -0,0 +1,23 @@
'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

View File

@ -5,5 +5,5 @@
@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
} }

View File

@ -17,7 +17,7 @@ import {
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

View File

@ -148,7 +148,7 @@ const Select: FC<ISelectProps> = ({
const SimpleSelect: FC<ISelectProps> = ({ const SimpleSelect: FC<ISelectProps> = ({
className, className,
wrapperClassName, wrapperClassName = '',
items = defaultItems, items = defaultItems,
defaultValue = 1, defaultValue = 1,
disabled = false, disabled = false,

View File

@ -15,10 +15,10 @@
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;

View File

@ -234,10 +234,12 @@ const FileUploader = ({
/> />
<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> <span>
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label> {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}/>}

View File

@ -103,7 +103,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
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">

View File

@ -15,9 +15,6 @@
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;

View File

@ -106,7 +106,7 @@ const StepOne = ({
<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,

View File

@ -5,6 +5,7 @@ import cn from 'classnames'
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 = {
@ -17,9 +18,12 @@ 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 && (
<> <>
@ -46,13 +50,13 @@ const StepThree = ({ datasetId, datasetName, indexingType, creationCache }: Step
/> />
</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>
) )
} }

View File

@ -1,5 +1,5 @@
.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;
@ -251,7 +251,7 @@
} }
.ruleItem { .ruleItem {
@apply flex items-center h-7; @apply flex items-center;
} }
.formFooter { .formFooter {
@ -382,7 +382,7 @@
.previewWrap { .previewWrap {
flex-shrink: 0; flex-shrink: 0;
width: 524px; max-width: 524px;
} }
.previewHeader { .previewHeader {

View File

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
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'
@ -20,6 +21,7 @@ import {
} 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'
@ -37,6 +39,8 @@ import I18n from '@/context/i18n'
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 = {
@ -84,6 +88,9 @@ const StepTwo = ({
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)
@ -467,7 +474,7 @@ const StepTwo = ({
useEffect(() => { useEffect(() => {
if (segmentationType === SegmentType.AUTO) { if (segmentationType === SegmentType.AUTO) {
setAutomaticFileIndexingEstimate(null) setAutomaticFileIndexingEstimate(null)
setShowPreview() !isMobile && setShowPreview()
fetchFileIndexingEstimate() fetchFileIndexingEstimate()
setPreviewSwitched(false) setPreviewSwitched(false)
} }
@ -493,8 +500,23 @@ const StepTwo = ({
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.pageHeader, scrolled && s.fixed, isMobile && '!px-6')}>
<div className={cn(s.form)}> <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
@ -554,7 +576,7 @@ const StepTwo = ({
</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}>
@ -574,7 +596,7 @@ const StepTwo = ({
</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(
@ -797,68 +819,69 @@ const StepTwo = ({
</div> </div>
</div> </div>
</div> </div>
{(showPreview) <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 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={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`)}> <div className='flex items-center justify-between px-8'>
<div className='flex items-center justify-between px-8'> <div className='grow flex items-center'>
<div className='grow flex items-center'> <div>{t('datasetCreation.stepTwo.previewTitle')}</div>
<div>{t('datasetCreation.stepTwo.previewTitle')}</div> {docForm === DocForm.QA && !previewSwitched && (
{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>
<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> <div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
<div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}> <XMarkIcon className='h-4 w-4'></XMarkIcon>
<XMarkIcon className='h-4 w-4'></XMarkIcon>
</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>
<div className='my-4 px-8 space-y-4'> {docForm === DocForm.QA && !previewSwitched && (
{previewSwitched && docForm === DocForm.QA && fileIndexingEstimate?.qa_preview && ( <div className='px-8 pr-12 text-xs text-gray-500'>
<> <span>{t('datasetCreation.stepTwo.previewSwitchTipStart')}</span>
{fileIndexingEstimate?.qa_preview.map((item, index) => ( <span className='text-amber-600'>{t('datasetCreation.stepTwo.previewSwitchTipEnd')}</span>
<PreviewItem type={PreviewType.QA} key={item.question} qa={item} index={index + 1} /> </div>
))} )}
</> </div>
)} <div className='my-4 px-8 space-y-4'>
{(docForm === DocForm.TEXT || !previewSwitched) && fileIndexingEstimate?.preview && ( {previewSwitched && docForm === DocForm.QA && fileIndexingEstimate?.qa_preview && (
<> <>
{fileIndexingEstimate?.preview.map((item, index) => ( {fileIndexingEstimate?.qa_preview.map((item, index) => (
<PreviewItem type={PreviewType.TEXT} key={item} content={item} index={index + 1} /> <PreviewItem type={PreviewType.QA} key={item.question} qa={item} index={index + 1} />
))} ))}
</> </>
)} )}
{previewSwitched && docForm === DocForm.QA && !fileIndexingEstimate?.qa_preview && ( {(docForm === DocForm.TEXT || !previewSwitched) && fileIndexingEstimate?.preview && (
<div className='flex items-center justify-center h-[200px]'> <>
<Loading type='area' /> {fileIndexingEstimate?.preview.map((item, index) => (
</div> <PreviewItem type={PreviewType.TEXT} key={item} content={item} index={index + 1} />
)} ))}
{!previewSwitched && !fileIndexingEstimate?.preview && ( </>
<div className='flex items-center justify-center h-[200px]'> )}
<Loading type='area' /> {previewSwitched && docForm === DocForm.QA && !fileIndexingEstimate?.qa_preview && (
</div> <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>}
{!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 className={cn(s.sideTip)}> </FloatRightContainer>
<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>
) )
} }

View File

@ -14,14 +14,15 @@
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 {

View File

@ -3,46 +3,56 @@ import { useTranslation } from 'react-i18next'
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, step: number
datasetId?: string, 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 className={cn(s.stepsHeader, isMobile && '!px-0 justify-center')}>
<div onClick={navBackHandle} className={s.navBack} /> <div onClick={navBackHandle} className={cn(s.navBack, isMobile && '!mr-0')} />
{!datasetId ? t('datasetCreation.steps.header.creation') : t('datasetCreation.steps.header.update')} {!isMobile && (!datasetId ? t('datasetCreation.steps.header.creation') : t('datasetCreation.steps.header.update'))}
</div> </div>
<div className={cn(s.stepList)}> <div className={cn(s.stepList, isMobile && '!p-0')}>
<div className={cn(s.stepItem, s.step1, step === 1 && s.active, step !== 1 && s.done)}> {STEP_LIST.map(item => (
<div className={cn(s.stepNum)}>{step === 1 ? 1 : ''}</div> <div
<div className={cn(s.stepName)}>{t('datasetCreation.steps.one')}</div> key={item}
</div> className={cn(s.stepItem, s[`step${item}`], step === item && s.active, step > item && s.done, isMobile && 'px-0')}
<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.stepNum)}>{item}</div>
<div className={cn(s.stepName)}>{t('datasetCreation.steps.two')}</div> <div className={cn(s.stepName)}>{isMobile ? '' : t(STEP_T_MAP[item])}</div>
</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> </div>
</div> </div>
) )

View File

@ -178,7 +178,7 @@ const SegmentDetailComponent: FC<ISegmentDetailProps> = ({
} }
</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>

View File

@ -8,7 +8,7 @@
@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);
@ -18,7 +18,7 @@
@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;
@ -48,7 +48,7 @@
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;

View File

@ -23,6 +23,8 @@ import { checkSegmentBatchImportProgress, fetchDocumentDetail, segmentBatchImpor
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: '' })
@ -50,10 +52,14 @@ type Props = {
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>()
@ -124,44 +130,46 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
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 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={'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 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 || ''} /> <div className='flex items-center flex-wrap gap-y-2'>
{embeddingAvailable && documentDetail && !documentDetail.archived && ( <StatusItem status={documentDetail?.display_status || 'available'} scene='detail' errorMessage={documentDetail?.error || ''} />
<SegmentAdd {embeddingAvailable && documentDetail && !documentDetail.archived && (
importStatus={importStatus} <SegmentAdd
clearProcessStatus={resetProcessStatus} importStatus={importStatus}
showNewSegmentModal={showNewSegmentModal} clearProcessStatus={resetProcessStatus}
showBatchModal={showBatchModal} 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
<OperationAction className={cn(style.layoutRightIcon, showMetadata ? style.iconShow : style.iconClose)}
scene='detail' onClick={() => setShowMetadata(!showMetadata)}
embeddingAvailable={embeddingAvailable} />
detail={{ </div>
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 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
@ -174,11 +182,13 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
} }
</div> </div>
} }
{showMetadata && <Metadata <FloatRightContainer showClose isOpen={showMetadata} onClose={() => setShowMetadata(false)} isMobile={isMobile} panelClassname='!justify-start' footer={null}>
docDetail={{ ...documentDetail, ...documentMetadata, doc_type: documentMetadata?.doc_type === 'others' ? '' : documentMetadata?.doc_type } as any} <Metadata
loading={isMetadataLoading} docDetail={{ ...documentDetail, ...documentMetadata, doc_type: documentMetadata?.doc_type === 'others' ? '' : documentMetadata?.doc_type } as any}
onUpdate={metadataMutate} loading={isMetadataLoading}
/>} onUpdate={metadataMutate}
/>
</FloatRightContainer>
</div> </div>
<BatchModal <BatchModal
isShow={batchModalVisible} isShow={batchModalVisible}

View File

@ -1,5 +1,5 @@
.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;

View File

@ -198,7 +198,7 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
<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]'
@ -207,7 +207,7 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
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')}

View File

@ -316,8 +316,8 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
} }
return ( return (
<> <div className='w-full h-full overflow-x-auto'>
<table className={`w-full border-collapse border-0 text-sm mt-3 ${s.documentTable}`}> <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>
@ -380,7 +380,7 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
})} })}
</tbody> </tbody>
</table> </table>
</> </div>
) )
} }

View File

@ -65,8 +65,8 @@ const HitDetail: FC<IHitDetailProps> = ({ segInfo, vectorInfo }) => {
} }
return ( return (
<div className={'flex flex-row'}> <div className='flex flex-row overflow-x-auto'>
<div className="flex-1 bg-gray-25 p-6"> <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 || ''}

View File

@ -1,11 +1,12 @@
'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'
@ -17,9 +18,11 @@ import type { HitTestingResponse, HitTesting as HitTestingType } from '@/models/
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
@ -39,6 +42,10 @@ const RecordsEmpty: FC = () => {
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 })
@ -63,6 +70,11 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
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}>
@ -74,6 +86,7 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
<Textarea <Textarea
datasetId={datasetId} datasetId={datasetId}
setHitResult={setHitResult} setHitResult={setHitResult}
onSubmit={showRightPanel}
onUpdateList={recordsMutate} onUpdateList={recordsMutate}
loading={submitLoading} loading={submitLoading}
setLoading={setSubmitLoading} setLoading={setSubmitLoading}
@ -131,53 +144,56 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
<RecordsEmpty /> <RecordsEmpty />
)} )}
</div> </div>
<div className={s.rightDiv}> <FloatRightContainer panelClassname='!justify-start !overflow-y-auto' showClose isMobile={isMobile} isOpen={isShowRightPanel} onClose={hideRightPanel} footer={null}>
{submitLoading <div className={cn(s.rightDiv, 'p-0 sm:px-8 sm:pt-[42px] sm:pb-[26px]')}>
? <div className={s.cardWrapper}> {submitLoading
<SegmentCard ? <div className={s.cardWrapper}>
loading={true} <SegmentCard
scene='hitTesting' loading={true}
className='h-[216px]' scene='hitTesting'
/> className='h-[216px]'
<SegmentCard />
loading={true} <SegmentCard
scene='hitTesting' loading={true}
className='h-[216px]' scene='hitTesting'
/> className='h-[216px]'
</div> />
: !hitResult?.records.length </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='h-full flex flex-col justify-center items-center'>
<div className='text-gray-300 text-[13px] mt-3'> <div className={cn(docStyle.commonIcon, docStyle.targetIcon, '!bg-gray-200 !h-14 !w-14')} />
{t('datasetHitTesting.hit.emptyTip')} <div className='text-gray-300 text-[13px] mt-3'>
</div> {t('datasetHitTesting.hit.emptyTip')}
</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> </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}

View File

@ -1,5 +1,5 @@
.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;
@ -8,7 +8,7 @@
@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;

View File

@ -1,4 +1,3 @@
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'
@ -13,7 +12,7 @@ import { hitTesting } from '@/service/datasets'
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
@ -24,9 +23,10 @@ type Props = {
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,
@ -37,7 +37,8 @@ const TextAreaWithButton: FC<Props> = ({
onClickRetrievalMethod, onClickRetrievalMethod,
retrievalConfig, retrievalConfig,
isEconomy, isEconomy,
}) => { onSubmit: _onSubmit,
}: TextAreaWithButtonIProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { indexingTechnique } = useContext(DatasetDetailContext) const { indexingTechnique } = useContext(DatasetDetailContext)
@ -55,6 +56,7 @@ const TextAreaWithButton: FC<Props> = ({
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

View File

@ -24,13 +24,13 @@ import { useModalContext } from '@/context/modal-context'
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(() => {
@ -118,14 +118,14 @@ const Form = () => {
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)}
/> />
@ -134,7 +134,7 @@ const Form = () => {
<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')}
@ -152,7 +152,7 @@ const Form = () => {
<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}
@ -167,7 +167,7 @@ const Form = () => {
<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}

View File

@ -5,7 +5,7 @@ import s from './index.module.css'
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
@ -40,7 +40,7 @@ const IndexMethodRadio = ({
] ]
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

View File

@ -5,7 +5,7 @@ import s from './index.module.css'
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
@ -36,7 +36,7 @@ const PermissionsRadio = ({
] ]
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

View File

@ -1,11 +1,11 @@
'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
@ -21,10 +21,10 @@ const DevelopMain = ({ appId, dictionary }: IDevelopMainProps) => {
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'> <div className='flex items-center flex-wrap gap-y-1'>
<InputCopy className={`flex-shrink-0 mr-1 w-60 ${s.w320}`} value={appDetail?.api_base_url}> <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>
@ -37,7 +37,7 @@ const DevelopMain = ({ appId, dictionary }: IDevelopMainProps) => {
<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>

View File

@ -16,10 +16,6 @@
width: 4rem; width: 4rem;
} }
.w320 {
width: 20rem;
}
.customApi { .customApi {
font-size: 11px; font-size: 11px;
} }

View File

@ -26,7 +26,7 @@ const InstalledApp: FC<IInstalledAppProps> = ({
} }
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} />

View File

@ -9,6 +9,7 @@ import ItemOperation from '@/app/components/explore/item-operation'
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
@ -21,6 +22,7 @@ export type IAppNavItemProps = {
} }
export default function AppNavItem({ export default function AppNavItem({
isMobile,
name, name,
id, id,
icon, icon,
@ -42,27 +44,30 @@ export default function AppNavItem({
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'> {isMobile && <AppIcon size='tiny' icon={icon} background={icon_background} />}
<AppIcon size='tiny' icon={icon} background={icon_background} /> {!isMobile && (
<div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div> <>
</div> <div className='flex items-center space-x-2 w-0 grow'>
{ <AppIcon size='tiny' icon={icon} background={icon_background} />
<div className='shrink-0 h-6' onClick={e => e.stopPropagation()}> <div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div>
<ItemOperation </div>
isPinned={isPinned} <div className='shrink-0 h-6' onClick={e => e.stopPropagation()}>
isItemHovering={isHovering} <ItemOperation
togglePin={togglePin} isPinned={isPinned}
isShowDelete={!uninstallable && !isSelected} isItemHovering={isHovering}
onDelete={() => onDelete(id)} togglePin={togglePin}
/> isShowDelete={!uninstallable && !isSelected}
</div> onDelete={() => onDelete(id)}
} />
</div>
</>
)}
</div> </div>
) )
} }

View File

@ -11,6 +11,7 @@ import Item from './app-nav-item'
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">
@ -36,9 +37,11 @@ const ChatIcon = () => (
</svg> </svg>
) )
const SideBar: FC<{ export type IExploreSideBarProps = {
controlUpdateInstalledApps: number controlUpdateInstalledApps: number
}> = ({ }
const SideBar: FC<IExploreSideBarProps> = ({
controlUpdateInstalledApps, controlUpdateInstalledApps,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -48,6 +51,9 @@ const SideBar: FC<{
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)
@ -84,28 +90,28 @@ const SideBar: FC<{
}, [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)',
@ -115,6 +121,7 @@ const SideBar: FC<{
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}

View File

@ -10,7 +10,6 @@ import produce from 'immer'
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'
@ -721,10 +720,10 @@ const Main: FC<IMainProps> = () => {
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)',
@ -744,8 +743,7 @@ const Main: FC<IMainProps> = () => {
)} )}
{/* main */} {/* main */}
<div className={cn( <div className={cn(
s.installedApp, 'h-full flex-grow flex flex-col overflow-y-auto',
'flex-grow flex flex-col overflow-y-auto',
) )
}> }>
{(!isNewConversation || isResponsing || errorHappened) && ( {(!isNewConversation || isResponsing || errorHappened) && (

View File

@ -8,7 +8,7 @@ import Config from '../config'
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">
@ -26,16 +26,16 @@ const Init: FC<IConfigProps> = ({
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>
) )

View File

@ -1,3 +0,0 @@
.installedApp {
height: calc(100vh - 74px);
}

View File

@ -2,7 +2,6 @@
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
@ -12,22 +11,16 @@ const HeaderWrapper = ({
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( {children}
s[`header-${langeniusVersionInfo.current_env}`],
'flex flex-1 items-center justify-between px-4',
)}>
{children}
</div>
</div> </div>
) )
} }

View File

@ -17,7 +17,11 @@ import { ArrowUpRight, ChevronDown } from '@/app/components/base/icons/src/vende
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
@ -50,12 +54,15 @@ export default function AppSelector() {
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} /> <Avatar name={userProfile.name} className='sm:mr-2 mr-0' size={32} />
{userProfile.name} {!isMobile && <>
<ChevronDown className="w-3 h-3 ml-1 text-gray-700"/> {userProfile.name}
<ChevronDown className="w-3 h-3 ml-1 text-gray-700"/>
</>}
</Menu.Button> </Menu.Button>
</div> </div>
<Transition <Transition

View File

@ -70,7 +70,7 @@ const ApiBasedExtensionSelector: FC<ApiBasedExtensionSelectorProps> = ({
) )
} }
</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'>

View File

@ -76,7 +76,7 @@ const DataSourceNotion = ({
: ( : (
<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}

View File

@ -23,6 +23,7 @@ import { User01 as User01Solid, Users01 as Users01Solid } from '@/app/components
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
@ -42,6 +43,10 @@ export default function AccountSetting({
}: 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',
@ -130,9 +135,9 @@ export default function AccountSetting({
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='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-2 text-base font-medium leading-6 text-gray-900'>{t('common.userProfile.settings')}</div> <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> <div className='w-full'>
{ {
menuItems.map(menuItem => ( menuItems.map(menuItem => (
<div key={menuItem.key} className='mb-4'> <div key={menuItem.key} className='mb-4'>
@ -150,7 +155,7 @@ export default function AccountSetting({
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>
)) ))
} }
@ -167,7 +172,7 @@ export default function AccountSetting({
<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 />}

View File

@ -51,13 +51,13 @@ const MembersPage = () => {
{t('common.members.invite')} {t('common.members.invite')}
</div> </div>
</div> </div>
<div> <div className='overflow-x-auto'>
<div className='flex items-center py-[7px] border-b border-gray-200'> <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'>

View File

@ -218,7 +218,7 @@ const ModelPage = () => {
} }
<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

View File

@ -34,7 +34,7 @@ const ModelItem: FC<ModelItemProps> = ({
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]}
{ {

View File

@ -159,7 +159,7 @@ const Form: FC<FormProps> = ({
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)}

View File

@ -96,7 +96,7 @@ const ModelModal: FC<ModelModalProps> = ({
<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>
@ -113,7 +113,7 @@ const ModelModal: FC<ModelModalProps> = ({
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'

View File

@ -1,43 +1,90 @@
'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'> <div className={classNames(
<Link href="/apps" className='flex items-center mr-4'> s[`header-${langeniusVersionInfo.current_env}`],
<LogoSite /> 'flex flex-1 items-center justify-between px-4',
</Link> )}>
<GithubStar /> <div className='flex items-center'>
</div> {isMobile && <div
<div className='flex items-center'> className='flex items-center justify-center h-8 w-8 cursor-pointer'
<ExploreNav className={navClassName} /> onClick={toggle}
<AppNav /> >
{isCurrentWorkspaceManager && <DatasetNav />} <Bars3Icon className="h-4 w-4 text-gray-500" />
</div> </div>}
<div className='flex items-center flex-shrink-0'> {!isMobile && <>
<EnvNav /> <Link href="/apps" className='flex items-center mr-4'>
<WorkspaceProvider> <LogoSite />
<AccountDropdown /> </Link>
</WorkspaceProvider> <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>
)}
</> </>
) )
} }

View File

@ -34,7 +34,7 @@ const Nav = ({
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'}
`}> `}>

View File

@ -11,7 +11,6 @@ import { useBoolean, useGetState } from 'ahooks'
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'
@ -683,7 +682,7 @@ const Main: FC<IMainProps> = ({
} }
return ( return (
<div className='bg-gray-100'> <div className='bg-gray-100 h-full'>
{!isInstalledApp && ( {!isInstalledApp && (
<Header <Header
title={siteInfo.title} title={siteInfo.title}
@ -720,7 +719,7 @@ const Main: FC<IMainProps> = ({
)} )}
{/* 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',
) )
}> }>

View File

@ -1,3 +0,0 @@
.installedApp {
height: calc(100vh - 74px);
}

View File

@ -10,7 +10,6 @@ import { useBoolean, useGetState } from 'ahooks'
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'
@ -561,7 +560,7 @@ const Main: FC<IMainProps> = ({
<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',
) )
}> }>

View File

@ -1,3 +0,0 @@
.installedApp {
height: calc(100vh - 74px);
}

View File

@ -33,7 +33,7 @@ const Header: FC<IHeaderProps> = ({
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
`} `}
> >
@ -52,7 +52,7 @@ const Header: FC<IHeaderProps> = ({
} }
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?.()}

View File

@ -1,5 +1,5 @@
.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);
} }

View File

@ -120,9 +120,9 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
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>

View File

@ -21,6 +21,7 @@ const translation = {
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',

View File

@ -21,6 +21,7 @@ const translation = {
operation: { operation: {
applyConfig: '发布', applyConfig: '发布',
resetConfig: '重置', resetConfig: '重置',
debugConfig: '调试',
addFeature: '添加功能', addFeature: '添加功能',
automatic: '自动编排', automatic: '自动编排',
stopResponding: '停止响应', stopResponding: '停止响应',

View File

@ -96,6 +96,7 @@ const translation = {
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',

View File

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