feat: fe mobile responsive next (#1609)
This commit is contained in:
parent
3cc697832a
commit
a9c1c7d239
89 changed files with 768 additions and 485 deletions
|
|
@ -39,10 +39,10 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||
const navigation = useMemo(() => {
|
||||
const navs = [
|
||||
{ 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.logAndAnn'), href: `/app/${appId}/logs`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
|
||||
].filter(nav => !!nav)
|
||||
]
|
||||
return navs
|
||||
}, [appId, isCurrentWorkspaceManager, t])
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||
return (
|
||||
<div className={cn(s.app, 'flex', 'overflow-hidden')}>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
|
|||
}
|
||||
|
||||
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
|
||||
appInfo={response}
|
||||
cardType="webapp"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const Overview = async ({
|
|||
*/
|
||||
const { t } = await translate(locale, 'app-overview')
|
||||
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 />
|
||||
<div className='flex flex-row items-center justify-between mb-4 text-xl text-gray-900'>
|
||||
{t('overview.title')}
|
||||
|
|
|
|||
|
|
@ -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') || ''}/>
|
||||
</div>
|
||||
|
||||
<div className='h-[247px] overflow-y-auto'>
|
||||
<div className='overflow-y-auto'>
|
||||
<div className={style.newItemCaption}>
|
||||
<h3 className='inline'>{t('app.newApp.captionAppType')}</h3>
|
||||
{isWithTemplate && (
|
||||
|
|
@ -139,7 +139,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
|||
</div>
|
||||
{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) => (
|
||||
<li
|
||||
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
|
||||
className={classNames(style.listItem, style.selectable, newAppMode === 'chat' && style.selected)}
|
||||
onClick={() => setNewAppMode('chat')}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import React, { useEffect } from 'react'
|
|||
import { usePathname } from 'next/navigation'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classNames from 'classnames'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
Cog8ToothIcon,
|
||||
// CommandLineIcon,
|
||||
|
|
@ -11,6 +13,8 @@ import {
|
|||
// eslint-disable-next-line sort-imports
|
||||
PuzzlePieceIcon,
|
||||
DocumentTextIcon,
|
||||
PaperClipIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import {
|
||||
Cog8ToothIcon as Cog8ToothSolidIcon,
|
||||
|
|
@ -20,29 +24,39 @@ import {
|
|||
import Link from 'next/link'
|
||||
import s from './style.module.css'
|
||||
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 AppSideBar from '@/app/components/app-sidebar'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import FloatPopoverContainer from '@/app/components/base/float-popover-container'
|
||||
import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
export type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
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',
|
||||
appStatus = true,
|
||||
detail,
|
||||
}) => {
|
||||
isMobile,
|
||||
}: ILikedItemProps) => {
|
||||
return (
|
||||
<Link className={s.itemWrapper} href={`/app/${detail?.id}/overview`}>
|
||||
<div className={s.iconWrapper}>
|
||||
<Link className={classNames(s.itemWrapper, 'px-0 sm:px-3 justify-center sm:justify-start')} href={`/app/${detail?.id}/overview`}>
|
||||
<div className={classNames(s.iconWrapper, 'mr-0 sm:mr-2')}>
|
||||
<AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background}/>
|
||||
{type === 'app' && (
|
||||
<div className={s.statusPoint}>
|
||||
|
|
@ -50,7 +64,7 @@ const LikedItem: FC<{ type?: 'plugin' | 'app'; appStatus?: boolean; detail: Rela
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={s.appInfo}>{detail?.name || '--'}</div>
|
||||
{!isMobile && <div className={s.appInfo}>{detail?.name || '--'}</div>}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
|
@ -83,6 +97,68 @@ const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
|
|||
</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 {
|
||||
children,
|
||||
|
|
@ -91,6 +167,10 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||
const pathname = usePathname()
|
||||
const hideSideBar = /documents\/create$/.test(pathname)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
|
||||
url: 'fetchDatasetDetail',
|
||||
datasetId,
|
||||
|
|
@ -113,54 +193,18 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||
document.title = `${datasetRes.name || 'Dataset'} - Dify`
|
||||
}, [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)
|
||||
return <Loading />
|
||||
|
||||
return (
|
||||
<div className='flex'>
|
||||
<div className='flex overflow-hidden'>
|
||||
{!hideSideBar && <AppSideBar
|
||||
title={datasetRes?.name || '--'}
|
||||
icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
|
||||
icon_background={datasetRes?.icon_background || '#F5F5F5'}
|
||||
desc={datasetRes?.description || '--'}
|
||||
navigation={navigation}
|
||||
extraInfo={<ExtraInfo />}
|
||||
extraInfo={<ExtraInfo isMobile={isMobile} relatedApps={relatedApps} />}
|
||||
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
|
||||
/>}
|
||||
<DatasetDetailContext.Provider value={{
|
||||
|
|
@ -168,7 +212,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||
dataset: datasetRes,
|
||||
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>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,14 +14,12 @@ const Settings = async ({
|
|||
const { t } = await useTranslation(locale, 'dataset-settings')
|
||||
|
||||
return (
|
||||
<div className='bg-white h-full'>
|
||||
<div className='bg-white h-full overflow-y-auto'>
|
||||
<div className='px-6 py-3'>
|
||||
<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>
|
||||
<div>
|
||||
<Form datasetId={datasetId} />
|
||||
</div>
|
||||
<Form datasetId={datasetId} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
.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 {
|
||||
@apply truncate text-gray-700 text-sm font-normal;
|
||||
}
|
||||
.iconWrapper {
|
||||
@apply relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-md;
|
||||
@apply relative w-6 h-6 bg-[#D5F5F6] rounded-md;
|
||||
}
|
||||
.statusPoint {
|
||||
@apply flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded;
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ const ApiServer: FC<ApiServerProps> = ({
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center mr-2 pl-1.5 pr-1 h-8 bg-white/80 border-[0.5px] border-white rounded-lg'>
|
||||
<div className='mr-0.5 px-1.5 h-5 border border-gray-200 text-[11px] text-gray-500 rounded-md'>{t('appApi.apiServer')}</div>
|
||||
<div className='px-1 w-[248px] text-[13px] font-medium text-gray-800'>{apiBaseUrl}</div>
|
||||
<div className='flex items-center flex-wrap gap-y-2'>
|
||||
<div className='flex items-center mr-2 pl-1.5 pr-1 h-8 bg-white/80 border-[0.5px] border-white rounded-lg leading-5'>
|
||||
<div className='mr-0.5 px-1.5 h-5 border border-gray-200 text-[11px] text-gray-500 rounded-md shrink-0'>{t('appApi.apiServer')}</div>
|
||||
<div className='px-1 truncate w-fit sm:w-[248px] text-[13px] font-medium text-gray-800'>{apiBaseUrl}</div>
|
||||
<div className='mx-1 w-[1px] h-[14px] bg-gray-200'></div>
|
||||
<CopyFeedback
|
||||
content={apiBaseUrl}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const Container = () => {
|
|||
|
||||
return (
|
||||
<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
|
||||
value={activeTab}
|
||||
onChange={newActiveTab => setActiveTab(newActiveTab)}
|
||||
|
|
@ -38,16 +38,14 @@ const Container = () => {
|
|||
{activeTab === 'api' && data && <ApiServer apiBaseUrl={data.api_base_url || ''} />}
|
||||
</div>
|
||||
|
||||
{activeTab === 'dataset'
|
||||
? (
|
||||
<>
|
||||
<Datasets containerRef={containerRef} />
|
||||
<DatasetFooter />
|
||||
</>
|
||||
)
|
||||
: (
|
||||
activeTab === 'api' && data && <Doc apiBaseUrl={data.api_base_url || ''} />
|
||||
)}
|
||||
{activeTab === 'dataset' && (
|
||||
<>
|
||||
<Datasets containerRef={containerRef} />
|
||||
<DatasetFooter />
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'api' && data && <Doc apiBaseUrl={data.api_base_url || ''} />}
|
||||
</div>
|
||||
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const Doc: FC<DocProps> = ({
|
|||
const { locale } = useContext(I18n)
|
||||
|
||||
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'
|
||||
? <TemplateEn apiBaseUrl={apiBaseUrl} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue