feat: linked app new ui

This commit is contained in:
Joel 2024-12-10 16:12:57 +08:00
parent 77185d9617
commit dd23f1093b
6 changed files with 100 additions and 65 deletions

View File

@ -20,14 +20,12 @@ import {
// CommandLineIcon as CommandLineSolidIcon, // CommandLineIcon as CommandLineSolidIcon,
DocumentTextIcon as DocumentTextSolidIcon, DocumentTextIcon as DocumentTextSolidIcon,
} from '@heroicons/react/24/solid' } from '@heroicons/react/24/solid'
import Link from 'next/link' import { RiInformation2Line } from '@remixicon/react'
import s from './style.module.css' import s from './style.module.css'
import classNames from '@/utils/classnames' import classNames from '@/utils/classnames'
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets' import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
import type { RelatedApp, RelatedAppResponse } from '@/models/datasets' import type { RelatedAppResponse } from '@/models/datasets'
import AppSideBar from '@/app/components/app-sidebar' import AppSideBar from '@/app/components/app-sidebar'
import Divider from '@/app/components/base/divider'
import AppIcon from '@/app/components/base/app-icon'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import FloatPopoverContainer from '@/app/components/base/float-popover-container' import FloatPopoverContainer from '@/app/components/base/float-popover-container'
import DatasetDetailContext from '@/context/dataset-detail' import DatasetDetailContext from '@/context/dataset-detail'
@ -35,57 +33,16 @@ import { DataSourceType } from '@/models/datasets'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { LanguagesSupported } from '@/i18n/language' import { LanguagesSupported } from '@/i18n/language'
import { useStore } from '@/app/components/app/store' import { useStore } from '@/app/components/app/store'
import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication'
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
import { getLocaleOnClient } from '@/i18n' import { getLocaleOnClient } from '@/i18n'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import Tooltip from '@/app/components/base/tooltip'
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
export type IAppDetailLayoutProps = { export type IAppDetailLayoutProps = {
children: React.ReactNode children: React.ReactNode
params: { datasetId: string } params: { datasetId: string }
} }
type ILikedItemProps = {
type?: 'plugin' | 'app'
appStatus?: boolean
detail: RelatedApp
isMobile: boolean
}
const LikedItem = ({
type = 'app',
detail,
isMobile,
}: ILikedItemProps) => {
return (
<Link className={classNames(s.itemWrapper, 'px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
<div className={classNames(s.iconWrapper, 'mr-0')}>
<AppIcon size='tiny' iconType={detail.icon_type} icon={detail.icon} background={detail.icon_background} imageUrl={detail.icon_url} />
{type === 'app' && (
<span className='absolute bottom-[-2px] right-[-2px] w-3.5 h-3.5 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
{detail.mode === 'advanced-chat' && (
<ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
)}
{detail.mode === 'agent-chat' && (
<CuteRobot className='w-2.5 h-2.5 text-indigo-600' />
)}
{detail.mode === 'chat' && (
<ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
)}
{detail.mode === 'completion' && (
<AiText className='w-2.5 h-2.5 text-[#0E9384]' />
)}
{detail.mode === 'workflow' && (
<Route className='w-2.5 h-2.5 text-[#f79009]' />
)}
</span>
)}
</div>
{!isMobile && <div className={classNames(s.appInfo, 'ml-2')}>{detail?.name || '--'}</div>}
</Link>
)
}
const TargetIcon = ({ className }: SVGProps<SVGElement>) => { const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}> return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
<g clipPath="url(#clip0_4610_6951)"> <g clipPath="url(#clip0_4610_6951)">
@ -124,23 +81,43 @@ const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile) const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
const { t } = useTranslation() const { t } = useTranslation()
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
const relatedAppsTotal = relatedApps?.data?.length || 0
useEffect(() => { useEffect(() => {
setShowTips(!isMobile) setShowTips(!isMobile)
}, [isMobile, setShowTips]) }, [isMobile, setShowTips])
return <div className='w-full flex flex-col items-center'> return <div>
<Divider className='mt-5' /> {hasRelatedApps && (
{(relatedApps?.data && relatedApps?.data?.length > 0) && (
<> <>
{!isMobile && <div className='w-full px-2 pb-1 pt-4 uppercase text-xs text-gray-500 font-medium'>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>} {!isMobile && (
<Tooltip
position='right'
noDecoration
needsDelay
popupContent={
<LinkedAppsPanel
relatedApps={relatedApps.data}
isMobile={isMobile}
/>
}
>
<div className='inline-flex items-center system-xs-medium-uppercase text-text-secondary space-x-1 cursor-pointer'>
<span>{relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')}</span>
<RiInformation2Line className='w-4 h-4' />
</div>
</Tooltip>
)}
{isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}> {isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
{relatedApps?.total || '--'} {relatedAppsTotal || '--'}
<PaperClipIcon className='h-4 w-4 text-gray-700' /> <PaperClipIcon className='h-4 w-4 text-gray-700' />
</div>} </div>}
{relatedApps?.data?.map((item, index) => (<LikedItem key={index} isMobile={isMobile} detail={item} />))}
</> </>
)} )}
{!relatedApps?.data?.length && ( {!hasRelatedApps && (
<FloatPopoverContainer <FloatPopoverContainer
placement='bottom-start' placement='bottom-start'
open={isShowTips} open={isShowTips}

View File

@ -1,12 +1,3 @@
.itemWrapper {
@apply flex items-center w-full h-10 rounded-lg hover:bg-gray-50 cursor-pointer;
}
.appInfo {
@apply truncate text-gray-700 text-sm font-normal;
}
.iconWrapper {
@apply relative w-6 h-6 rounded-lg;
}
.statusPoint { .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

@ -13,6 +13,7 @@ type Props = {
name: string name: string
description: string description: string
expand: boolean expand: boolean
extraInfo?: React.ReactNode
} }
const DatasetInfo: FC<Props> = ({ const DatasetInfo: FC<Props> = ({
@ -20,6 +21,7 @@ const DatasetInfo: FC<Props> = ({
description, description,
isExternal, isExternal,
expand, expand,
extraInfo,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -36,6 +38,7 @@ const DatasetInfo: FC<Props> = ({
<div className='my-3 system-xs-regular text-text-tertiary'>{description}</div> <div className='my-3 system-xs-regular text-text-tertiary'>{description}</div>
</div> </div>
)} )}
{extraInfo}
</div> </div>
) )
} }

View File

@ -71,6 +71,7 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
description={desc} description={desc}
isExternal={isExternal} isExternal={isExternal}
expand={expand} expand={expand}
extraInfo={extraInfo && extraInfo(appSidebarExpand)}
/> />
)} )}
{!['app', 'dataset'].includes(iconType) && ( {!['app', 'dataset'].includes(iconType) && (
@ -99,7 +100,6 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
<NavLink key={index} mode={appSidebarExpand} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} /> <NavLink key={index} mode={appSidebarExpand} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
) )
})} })}
{extraInfo && extraInfo(appSidebarExpand)}
</nav> </nav>
{ {
!isMobile && ( !isMobile && (

View File

@ -0,0 +1,62 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import { RiArrowRightUpLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import AppIcon from '@/app/components/base/app-icon'
import type { RelatedApp } from '@/models/datasets'
type ILikedItemProps = {
appStatus?: boolean
detail: RelatedApp
isMobile: boolean
}
const appTypeMap = {
'chat': 'Chatbot',
'completion': 'Completion',
'agent-chat': 'Agent',
'advanced-chat': 'Chatflow',
'workflow': 'Workflow',
}
const LikedItem = ({
detail,
isMobile,
}: ILikedItemProps) => {
return (
<Link className={cn('group/link-item flex items-center justify-between w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
<div className='flex items-center'>
<div className={cn('relative w-6 h-6 rounded-md')}>
<AppIcon size='tiny' iconType={detail.icon_type} icon={detail.icon} background={detail.icon_background} imageUrl={detail.icon_url} />
</div>
{!isMobile && <div className={cn(' ml-2 truncate system-sm-medium text-text-primary')}>{detail?.name || '--'}</div>}
</div>
<div className='group-hover/link-item:hidden shrink-0 system-2xs-medium-uppercase text-text-tertiary'>{appTypeMap[detail.mode]}</div>
<RiArrowRightUpLine className='hidden group-hover/link-item:block w-4 h-4 text-text-tertiary' />
</Link>
)
}
type Props = {
relatedApps: RelatedApp[]
isMobile: boolean
}
const LinkedAppsPanel: FC<Props> = ({
relatedApps,
isMobile,
}) => {
const { t } = useTranslation()
return (
<div className='p-1 w-[320px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg rounded-xl backdrop-blur-[5px]'>
<div className='mt-1 mb-0.5 pl-2 system-xs-medium-uppercase text-text-tertiary'>{relatedApps.length || '--'} {t('common.datasetMenus.relatedApp')}</div>
{relatedApps.map((item, index) => (
<LikedItem key={index} detail={item} isMobile={isMobile} />
))}
</div>
)
}
export default React.memo(LinkedAppsPanel)

View File

@ -14,6 +14,7 @@ export type TooltipProps = {
popupContent?: React.ReactNode popupContent?: React.ReactNode
children?: React.ReactNode children?: React.ReactNode
popupClassName?: string popupClassName?: string
noDecoration?: boolean
offset?: OffsetOptions offset?: OffsetOptions
needsDelay?: boolean needsDelay?: boolean
asChild?: boolean asChild?: boolean
@ -27,6 +28,7 @@ const Tooltip: FC<TooltipProps> = ({
popupContent, popupContent,
children, children,
popupClassName, popupClassName,
noDecoration,
offset, offset,
asChild = true, asChild = true,
needsDelay = false, needsDelay = false,
@ -96,7 +98,7 @@ const Tooltip: FC<TooltipProps> = ({
> >
{popupContent && (<div {popupContent && (<div
className={cn( className={cn(
'relative px-3 py-2 text-xs font-normal text-text-secondary bg-components-tooltip-bg rounded-md shadow-lg break-words', !noDecoration && 'relative px-3 py-2 text-xs font-normal text-text-secondary bg-components-tooltip-bg rounded-md shadow-lg break-words',
popupClassName, popupClassName,
)} )}
onMouseEnter={() => triggerMethod === 'hover' && setHoverPopup()} onMouseEnter={() => triggerMethod === 'hover' && setHoverPopup()}