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,15 +14,13 @@ 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">
@ -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>
{t('datasetCreation.stepOne.uploader.button')}
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label> <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,9 +819,8 @@ 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'>
@ -845,9 +866,9 @@ const StepTwo = ({
</div> </div>
)} )}
</div> </div>
</div> </div>}
) {!showPreview && (
: (<div className={cn(s.sideTip)}> <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.stepTwo.sideTipTitle')}</div> <div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div>
@ -858,7 +879,9 @@ const StepTwo = ({
<p>{t('datasetCreation.stepTwo.sideTipP4')}</p> <p>{t('datasetCreation.stepTwo.sideTipP4')}</p>
</div> </div>
</div> </div>
</div>)} </div>
)}
</FloatRightContainer>
</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 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> </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,12 +130,13 @@ 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} />
<div className='flex items-center flex-wrap gap-y-2'>
<StatusItem status={documentDetail?.display_status || 'available'} scene='detail' errorMessage={documentDetail?.error || ''} /> <StatusItem status={documentDetail?.display_status || 'available'} scene='detail' errorMessage={documentDetail?.error || ''} />
{embeddingAvailable && documentDetail && !documentDetail.archived && ( {embeddingAvailable && documentDetail && !documentDetail.archived && (
<SegmentAdd <SegmentAdd
@ -158,10 +165,11 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
onClick={() => setShowMetadata(!showMetadata)} onClick={() => setShowMetadata(!showMetadata)}
/> />
</div> </div>
</div>
<div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}> <div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}>
{isDetailLoading {isDetailLoading
? <Loading type='app' /> ? <Loading type='app' />
: <div className={`box-border h-full w-full overflow-y-scroll ${embedding ? 'py-12 px-16' : 'pb-[30px] pt-3 px-6'}`}> : <div className={`h-full w-full flex flex-col ${embedding ? 'px-6 py-3 sm:py-12 sm:px-16' : 'pb-[30px] pt-3 px-6'}`}>
{embedding {embedding
? <Embedding detail={documentDetail} detailUpdate={detailMutate} /> ? <Embedding detail={documentDetail} detailUpdate={detailMutate} />
: <Completed : <Completed
@ -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}>
<Metadata
docDetail={{ ...documentDetail, ...documentMetadata, doc_type: documentMetadata?.doc_type === 'others' ? '' : documentMetadata?.doc_type } as any} docDetail={{ ...documentDetail, ...documentMetadata, doc_type: documentMetadata?.doc_type === 'others' ? '' : documentMetadata?.doc_type } as any}
loading={isMetadataLoading} loading={isMetadataLoading}
onUpdate={metadataMutate} 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,7 +144,8 @@ 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}>
<div className={cn(s.rightDiv, 'p-0 sm:px-8 sm:pt-[42px] sm:pb-[26px]')}>
{submitLoading {submitLoading
? <div className={s.cardWrapper}> ? <div className={s.cardWrapper}>
<SegmentCard <SegmentCard
@ -176,8 +190,10 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
) )
} }
</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,17 +44,19 @@ 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().
}} }}
> >
{isMobile && <AppIcon size='tiny' icon={icon} background={icon_background} />}
{!isMobile && (
<>
<div className='flex items-center space-x-2 w-0 grow'> <div className='flex items-center space-x-2 w-0 grow'>
<AppIcon size='tiny' icon={icon} background={icon_background} /> <AppIcon size='tiny' icon={icon} background={icon_background} />
<div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div> <div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div>
</div> </div>
{
<div className='shrink-0 h-6' onClick={e => e.stopPropagation()}> <div className='shrink-0 h-6' onClick={e => e.stopPropagation()}>
<ItemOperation <ItemOperation
isPinned={isPinned} isPinned={isPinned}
@ -62,7 +66,8 @@ export default function AppNavItem({
onDelete={() => onDelete(id)} onDelete={() => onDelete(id)}
/> />
</div> </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,23 +11,17 @@ 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(
s[`header-${langeniusVersionInfo.current_env}`],
'flex flex-1 items-center justify-between px-4',
)}>
{children} {children}
</div> </div>
</div>
) )
} }
export default HeaderWrapper export default HeaderWrapper

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