feat: fe mobile responsive next (#1609)
This commit is contained in:
parent
3cc697832a
commit
a9c1c7d239
@ -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,15 +14,13 @@ 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>
|
||||
</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'
|
||||
? (
|
||||
{activeTab === 'dataset' && (
|
||||
<>
|
||||
<Datasets containerRef={containerRef} />
|
||||
<DatasetFooter />
|
||||
</>
|
||||
)
|
||||
: (
|
||||
activeTab === 'api' && data && <Doc apiBaseUrl={data.api_base_url || ''} />
|
||||
)}
|
||||
|
||||
{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} />
|
||||
|
@ -15,6 +15,7 @@ export type IAppBasicProps = {
|
||||
hoverTip?: string
|
||||
textStyle?: { main?: string; extra?: string }
|
||||
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">
|
||||
@ -55,7 +56,7 @@ const ICON_MAP = {
|
||||
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 (
|
||||
<div className="flex items-start">
|
||||
{icon && icon_background && iconType === 'app' && (
|
||||
@ -69,7 +70,7 @@ export default function AppBasic({ icon, icon_background, name, type, hoverTip,
|
||||
</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 ?? ''}`}>
|
||||
{name}
|
||||
{hoverTip
|
||||
@ -78,7 +79,7 @@ export default function AppBasic({ icon, icon_background, name, type, hoverTip,
|
||||
</Tooltip>}
|
||||
</div>
|
||||
<div className={`text-xs font-normal text-gray-500 group-hover:text-gray-700 break-all ${textStyle?.extra ?? ''}`}>{type}</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import NavLink from './navLink'
|
||||
import AppBasic from './basic'
|
||||
|
||||
import type { NavIcon } from './navLink'
|
||||
import AppBasic from './basic'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
export type IAppDetailNavProps = {
|
||||
iconType?: 'app' | 'dataset' | 'notion'
|
||||
@ -20,15 +20,19 @@ export type 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 (
|
||||
<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">
|
||||
<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>
|
||||
<nav className="flex-1 p-4 space-y-1 bg-white">
|
||||
{navigation.map((item, index) => {
|
||||
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}
|
||||
|
@ -18,12 +18,14 @@ export type NavLinkProps = {
|
||||
selected: NavIcon
|
||||
normal: NavIcon
|
||||
}
|
||||
mode?: 'expand' | 'collapse'
|
||||
}
|
||||
|
||||
export default function NavLink({
|
||||
name,
|
||||
href,
|
||||
iconMap,
|
||||
mode = 'expand',
|
||||
}: NavLinkProps) {
|
||||
const segment = useSelectedLayoutSegment()
|
||||
const isActive = href.toLowerCase().split('/')?.pop() === segment?.toLowerCase()
|
||||
@ -45,7 +47,7 @@ export default function NavLink({
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{name}
|
||||
{mode === 'expand' && name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import Loading from '@/app/components/base/loading'
|
||||
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 { useProviderContext } from '@/context/provider-context'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import type { ModelModeType } from '@/types/app'
|
||||
export type IConfigModelProps = {
|
||||
isAdvancedMode: boolean
|
||||
@ -54,6 +55,10 @@ const ConfigModel: FC<IConfigModelProps> = ({
|
||||
const [maxTokenSettingTipVisible, setMaxTokenSettingTipVisible] = useState(false)
|
||||
const configContentRef = React.useRef(null)
|
||||
const currModel = textGenerationModelList.find(item => item.model_name === modelId)
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
// Cache loaded model param
|
||||
const [allParams, setAllParams, getAllParams] = useGetState<Record<string, Record<string, any>>>({})
|
||||
const currParams = allParams[provider]?.[modelId]
|
||||
@ -288,7 +293,7 @@ const ConfigModel: FC<IConfigModelProps> = ({
|
||||
</div>
|
||||
{isShowConfig && (
|
||||
<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
|
||||
headerIcon={
|
||||
<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}>
|
||||
<Radio
|
||||
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
|
||||
? ({
|
||||
1: 'text-[#6938EF]',
|
||||
@ -351,7 +356,7 @@ const ConfigModel: FC<IConfigModelProps> = ({
|
||||
>
|
||||
<>
|
||||
{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>
|
||||
</>
|
||||
</Radio>
|
||||
@ -361,12 +366,12 @@ const ConfigModel: FC<IConfigModelProps> = ({
|
||||
</>
|
||||
<Radio
|
||||
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]')}
|
||||
>
|
||||
<>
|
||||
{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.Group>
|
||||
|
@ -20,7 +20,7 @@ const ModelModeTypeLabel: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<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}`)}
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ const ModelName: FC<IModelNameProps> = ({
|
||||
modelDisplayName,
|
||||
}) => {
|
||||
return (
|
||||
<span title={modelDisplayName}>
|
||||
<span className='text-ellipsis overflow-hidden whitespace-nowrap' title={modelDisplayName}>
|
||||
{modelDisplayName}
|
||||
</span>
|
||||
)
|
||||
|
@ -49,7 +49,7 @@ const ParamItem: FC<IParamIteProps> = ({ id, name, tip, step = 0.1, min = 0, max
|
||||
onChange(id, getFitPrecisionValue(value, precision))
|
||||
}, [value, precision])
|
||||
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 items-center">
|
||||
<span className="mr-[6px] text-gray-500 text-[13px] font-medium">{name}</span>
|
||||
|
@ -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>
|
||||
)}
|
||||
{hasVar && (
|
||||
<div className='rounded-lg border border-gray-200 bg-white'>
|
||||
<table className={`${s.table} w-full border-collapse border-0 rounded-lg text-sm`}>
|
||||
<div className='rounded-lg border border-gray-200 bg-white overflow-x-auto'>
|
||||
<table className={`${s.table} min-w-[440px] max-w-full border-collapse border-0 rounded-lg text-sm`}>
|
||||
<thead className="border-b border-gray-200 text-gray-500 text-xs font-medium">
|
||||
<tr className='uppercase'>
|
||||
<td>{t('appDebug.variableTable.key')}</td>
|
||||
|
@ -31,7 +31,7 @@ const ParamsConfig: FC = () => {
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<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 />
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
|
@ -16,6 +16,7 @@ import Loading from '@/app/components/base/loading'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
// type
|
||||
import type { AutomaticRes } from '@/service/debug'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
const noDataIcon = (
|
||||
<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 media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const [audiences, setAudiences] = React.useState<string>('')
|
||||
const [hopingToSolve, setHopingToSolve] = React.useState<string>('')
|
||||
const isValid = () => {
|
||||
@ -103,15 +107,36 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
|
||||
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 (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className='min-w-[1120px] !p-0'
|
||||
className='!p-0 sm:min-w-[768px] xl:min-w-[1120px]'
|
||||
closable
|
||||
>
|
||||
<div className='flex h-[680px]'>
|
||||
<div className='w-[480px] shrink-0 px-8 py-6 h-full overflow-y-auto border-r border-gray-100'>
|
||||
<div className='flex h-[680px] flex-wrap gap-y-4 overflow-y-auto'>
|
||||
{isShowAutoPromptInput() && <div className='w-full sm:w-[360px] xl:w-[480px] shrink-0 px-8 py-6 h-full overflow-y-auto border-r border-gray-100'>
|
||||
<div>
|
||||
<div 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>
|
||||
@ -139,7 +164,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
{(!isLoading && res) && (
|
||||
<div className='grow px-8 pt-6 h-full overflow-y-auto'>
|
||||
@ -180,7 +205,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
</div>
|
||||
)}
|
||||
{isLoading && renderLoading}
|
||||
{(!isLoading && !res) && renderNoData}
|
||||
{isShowAutoPromptResPlaceholder() && renderNoData}
|
||||
{showConfirmOverwrite && (
|
||||
<Confirm
|
||||
title={t('appDebug.automatic.overwriteTitle')}
|
||||
|
@ -9,6 +9,8 @@ import { formatNumber } from '@/utils/format'
|
||||
import FileIcon from '@/app/components/base/file-icon'
|
||||
import { Settings01, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
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 = {
|
||||
className?: string
|
||||
@ -24,6 +26,10 @@ const Item: FC<ItemProps> = ({
|
||||
onRemove,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const [showSettingsModal, setShowSettingsModal] = useState(false)
|
||||
|
||||
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]' />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
showSettingsModal && (
|
||||
<Drawer isOpen={showSettingsModal} onClose={() => setShowSettingsModal(false)} footer={null} mask={isMobile} panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'>
|
||||
<SettingsModal
|
||||
currentDataset={config}
|
||||
onCancel={() => setShowSettingsModal(false)}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Drawer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ const ParamsConfig: FC = () => {
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
className='min-w-[528px]'
|
||||
className='sm:min-w-[528px]'
|
||||
wrapperClassName='z-50'
|
||||
title={t('appDebug.datasetConfig.settingTitle')}
|
||||
>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useClickAway } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import cn from 'classnames'
|
||||
@ -30,7 +29,7 @@ type SettingsModalProps = {
|
||||
}
|
||||
|
||||
const rowClass = `
|
||||
flex justify-between py-4
|
||||
flex justify-between py-4 flex-wrap gap-y-2
|
||||
`
|
||||
|
||||
const labelClass = `
|
||||
@ -45,10 +44,6 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const ref = useRef(null)
|
||||
useClickAway(() => {
|
||||
if (ref)
|
||||
onCancel()
|
||||
}, ref)
|
||||
|
||||
const { setShowAccountSettingModal } = useModalContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
@ -122,10 +117,8 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
|
||||
return (
|
||||
<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={{
|
||||
zIndex: 11,
|
||||
width: 700,
|
||||
height: 'calc(100vh - 72px)',
|
||||
}}
|
||||
ref={ref}
|
||||
@ -179,12 +172,12 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.permissions')}</div>
|
||||
</div>
|
||||
<div className='w-[480px]'>
|
||||
<div className='w-full sm:w-[480px]'>
|
||||
<PermissionsRadio
|
||||
disable={!localeCurrentDataset?.embedding_available}
|
||||
value={localeCurrentDataset.permission}
|
||||
onChange={v => handleValueChange('permission', v!)}
|
||||
itemClassName='!w-[227px]'
|
||||
itemClassName='sm:!w-[227px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -198,7 +191,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
disable={!localeCurrentDataset?.embedding_available}
|
||||
value={indexMethod}
|
||||
onChange={v => setIndexMethod(v!)}
|
||||
itemClassName='!w-[227px]'
|
||||
itemClassName='sm:!w-[227px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -272,7 +265,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
)}
|
||||
|
||||
<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={{
|
||||
borderColor: 'rgba(0, 0, 0, 0.05)',
|
||||
}}
|
||||
|
@ -8,6 +8,7 @@ import produce from 'immer'
|
||||
import { useBoolean, useGetState } from 'ahooks'
|
||||
import cn from 'classnames'
|
||||
import { clone, isEqual } from 'lodash-es'
|
||||
import { CodeBracketIcon } from '@heroicons/react/20/solid'
|
||||
import Button from '../../base/button'
|
||||
import Loading from '../../base/loading'
|
||||
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 I18n from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
|
||||
type PublichConfig = {
|
||||
modelConfig: ModelConfig
|
||||
@ -64,6 +67,10 @@ const Configuration: FC = () => {
|
||||
|
||||
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 [controlClearChatMessage, setControlClearChatMessage] = useState(0)
|
||||
const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({
|
||||
@ -600,7 +607,7 @@ const Configuration: FC = () => {
|
||||
>
|
||||
<>
|
||||
<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={s.promptTitle}></div>
|
||||
<div className='flex items-center h-[14px] space-x-1 text-xs'>
|
||||
@ -630,7 +637,7 @@ const Configuration: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center flex-wrap gap-y-2 gap-x-2'>
|
||||
{/* Model and Parameters */}
|
||||
<ConfigModel
|
||||
isAdvancedMode={isAdvancedMode}
|
||||
@ -644,22 +651,28 @@ const Configuration: FC = () => {
|
||||
}}
|
||||
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>
|
||||
{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>
|
||||
</div>
|
||||
</div>
|
||||
<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 />
|
||||
</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
|
||||
hasSetAPIKEY={hasSetAPIKEY}
|
||||
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
|
||||
inputs={inputs}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
{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>
|
||||
)
|
||||
|
@ -39,7 +39,7 @@ const Filter: FC<IFilterProps> = ({ appId, queryParams, setQueryParams }: IFilte
|
||||
if (!data)
|
||||
return null
|
||||
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
|
||||
items={TIME_PERIOD_LIST.map(item => ({ value: item.value, name: t(`appLog.filter.period.${item.name}`) }))}
|
||||
className='mt-0 !w-40'
|
||||
@ -47,7 +47,7 @@ const Filter: FC<IFilterProps> = ({ appId, queryParams, setQueryParams }: IFilte
|
||||
setQueryParams({ ...queryParams, period: item.value })
|
||||
}}
|
||||
defaultValue={queryParams.period} />
|
||||
<div className="relative ml-4 rounded-md mr-4">
|
||||
<div className="relative rounded-md">
|
||||
<SimpleSelect
|
||||
defaultValue={'all'}
|
||||
className='!w-[300px]'
|
||||
|
@ -33,6 +33,7 @@ import { TONE_LIST } from '@/config'
|
||||
import ModelIcon from '@/app/components/app/configuration/config-model/model-icon'
|
||||
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 useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
type IConversationList = {
|
||||
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-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 className='flex items-center'>
|
||||
<div className='flex items-center flex-wrap gap-y-1 justify-end'>
|
||||
<div
|
||||
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 { t } = useTranslation()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const [showDrawer, setShowDrawer] = useState<boolean>(false) // Whether to display the chat details drawer
|
||||
const [currentConversation, setCurrentConversation] = useState<ChatConversationGeneralDetail | CompletionConversationGeneralDetail | undefined>() // Currently selected conversation
|
||||
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 (
|
||||
<>
|
||||
<table className={`w-full border-collapse border-0 text-sm mt-3 ${s.logTable}`}>
|
||||
<div className='overflow-x-auto'>
|
||||
<table className={`w-full min-w-[440px] border-collapse border-0 text-sm mt-3 ${s.logTable}`}>
|
||||
<thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-bold">
|
||||
<tr>
|
||||
<td className='w-[1.375rem]'></td>
|
||||
<td>{t('appLog.table.header.time')}</td>
|
||||
<td>{t('appLog.table.header.endUser')}</td>
|
||||
<td>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td>
|
||||
<td>{isChatMode ? t('appLog.table.header.messageCount') : t('appLog.table.header.output')}</td>
|
||||
<td>{t('appLog.table.header.userRate')}</td>
|
||||
<td>{t('appLog.table.header.adminRate')}</td>
|
||||
<td className='w-[1.375rem] whitespace-nowrap'></td>
|
||||
<td className='whitespace-nowrap'>{t('appLog.table.header.time')}</td>
|
||||
<td className='whitespace-nowrap'>{t('appLog.table.header.endUser')}</td>
|
||||
<td className='whitespace-nowrap'>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td>
|
||||
<td className='whitespace-nowrap'>{isChatMode ? t('appLog.table.header.messageCount') : t('appLog.table.header.output')}</td>
|
||||
<td className='whitespace-nowrap'>{t('appLog.table.header.userRate')}</td>
|
||||
<td className='whitespace-nowrap'>{t('appLog.table.header.adminRate')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-gray-500">
|
||||
@ -504,9 +509,9 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
||||
<Drawer
|
||||
isOpen={showDrawer}
|
||||
onClose={onCloseDrawer}
|
||||
mask={false}
|
||||
mask={isMobile}
|
||||
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={{
|
||||
onClose: onCloseDrawer,
|
||||
@ -518,7 +523,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
||||
}
|
||||
</DrawerContext.Provider>
|
||||
</Drawer>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ function AppCard({
|
||||
|
||||
return (
|
||||
<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 ?? ''
|
||||
}`}
|
||||
>
|
||||
@ -163,8 +163,8 @@ function AppCard({
|
||||
: t('appOverview.overview.apiInfo.accessibleAddress')}
|
||||
</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="h-4 px-2 justify-start items-start gap-2 flex flex-1">
|
||||
<div className="text-gray-700 text-xs font-medium">
|
||||
<div className="h-4 px-2 justify-start items-start gap-2 flex flex-1 min-w-0">
|
||||
<div className="text-gray-700 text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap">
|
||||
{isApp ? appUrl : apiUrl}
|
||||
</div>
|
||||
</div>
|
||||
@ -196,7 +196,7 @@ function AppCard({
|
||||
</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} />}
|
||||
{OPERATIONS_MAP[cardType].map((op) => {
|
||||
const disabled
|
||||
|
@ -81,10 +81,10 @@ const CustomizeModal: FC<IShareLinkProps> = ({
|
||||
</div>
|
||||
<div className='flex py-4'>
|
||||
<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-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_KEY={'\'<Web API Key From Dify>\''} <br />
|
||||
NEXT_PUBLIC_API_URL={`'${api_base_url}'`}
|
||||
|
@ -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">
|
||||
{t(`${prefixEmbedded}.explanation`)}
|
||||
</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) => {
|
||||
return (
|
||||
<div
|
||||
@ -150,7 +150,7 @@ const Embedded = ({ isShow, onClose, appBaseUrl, accessToken }: Props) => {
|
||||
</Tooltip>
|
||||
</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">
|
||||
<pre className='select-text'>{OPTION_MAP[option].getContent(appBaseUrl, accessToken, isTestEnv)}</pre>
|
||||
</div>
|
||||
|
@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import Button from '../button'
|
||||
|
||||
type DrawerProps = {
|
||||
export type IDrawerProps = {
|
||||
title?: string
|
||||
description?: string
|
||||
panelClassname?: string
|
||||
@ -12,6 +13,7 @@ type DrawerProps = {
|
||||
mask?: boolean
|
||||
isOpen: boolean
|
||||
// closable: boolean
|
||||
showClose?: boolean
|
||||
onClose: () => void
|
||||
onCancel?: () => void
|
||||
onOk?: () => void
|
||||
@ -24,11 +26,12 @@ export default function Drawer({
|
||||
children,
|
||||
footer,
|
||||
mask = true,
|
||||
showClose = false,
|
||||
isOpen,
|
||||
onClose,
|
||||
onCancel,
|
||||
onOk,
|
||||
}: DrawerProps) {
|
||||
}: IDrawerProps) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Dialog
|
||||
@ -52,6 +55,9 @@ export default function Drawer({
|
||||
>
|
||||
{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>}
|
||||
{children}
|
||||
</>
|
||||
|
37
web/app/components/base/float-popover-container/index.tsx
Normal file
37
web/app/components/base/float-popover-container/index.tsx
Normal 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
|
23
web/app/components/base/float-right-container/index.tsx
Normal file
23
web/app/components/base/float-right-container/index.tsx
Normal 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
|
@ -5,5 +5,5 @@
|
||||
@apply absolute z-10 w-full max-w-sm px-4 mt-1 sm:px-0 lg:max-w-3xl
|
||||
}
|
||||
.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
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
|
||||
import type { OffsetOptions, Placement } from '@floating-ui/react'
|
||||
|
||||
type PortalToFollowElemOptions = {
|
||||
export type PortalToFollowElemOptions = {
|
||||
/*
|
||||
* top, bottom, left, right
|
||||
* start, end. Default is middle
|
||||
|
@ -148,7 +148,7 @@ const Select: FC<ISelectProps> = ({
|
||||
|
||||
const SimpleSelect: FC<ISelectProps> = ({
|
||||
className,
|
||||
wrapperClassName,
|
||||
wrapperClassName = '',
|
||||
items = defaultItems,
|
||||
defaultValue = 1,
|
||||
disabled = false,
|
||||
|
@ -15,10 +15,10 @@
|
||||
color: #667085;
|
||||
}
|
||||
.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;
|
||||
max-width: 640px;
|
||||
height: 80px;
|
||||
min-height: 80px;
|
||||
background: #F9FAFB;
|
||||
border: 1px dashed #EAECF0;
|
||||
border-radius: 12px;
|
||||
|
@ -234,10 +234,12 @@ const FileUploader = ({
|
||||
/>
|
||||
<div className={cn(s.title, titleClassName)}>{t('datasetCreation.stepOne.uploader.title')}</div>
|
||||
<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>{t('datasetCreation.stepOne.uploader.button')}</span>
|
||||
<span>
|
||||
{t('datasetCreation.stepOne.uploader.button')}
|
||||
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
|
||||
</span>
|
||||
</div>
|
||||
<div className={s.tip}>{t('datasetCreation.stepOne.uploader.tip', { size: fileUploadConfig.file_size_limit })}</div>
|
||||
{dragging && <div ref={dragRef} className={s.draggingCover}/>}
|
||||
|
@ -103,7 +103,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
|
||||
|
||||
return (
|
||||
<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} />
|
||||
</div>
|
||||
<div className="grow bg-white">
|
||||
|
@ -15,9 +15,6 @@
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.dataSourceTypeList {
|
||||
@apply flex items-center mb-8;
|
||||
}
|
||||
.dataSourceItem {
|
||||
@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;
|
||||
|
@ -106,7 +106,7 @@ const StepOne = ({
|
||||
<div className={s.form}>
|
||||
{
|
||||
shouldShowDataSourceTypeList && (
|
||||
<div className={s.dataSourceTypeList}>
|
||||
<div className='flex items-center mb-8 flex-wrap gap-y-4'>
|
||||
<div
|
||||
className={cn(
|
||||
s.dataSourceItem,
|
||||
|
@ -5,6 +5,7 @@ import cn from 'classnames'
|
||||
import EmbeddingProcess from '../embedding-process'
|
||||
|
||||
import s from './index.module.css'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import type { FullDocumentDetail, createDocumentResponse } from '@/models/datasets'
|
||||
|
||||
type StepThreeProps = {
|
||||
@ -17,9 +18,12 @@ type StepThreeProps = {
|
||||
const StepThree = ({ datasetId, datasetName, indexingType, creationCache }: StepThreeProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
return (
|
||||
<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]'>
|
||||
{!datasetId && (
|
||||
<>
|
||||
@ -46,13 +50,13 @@ const StepThree = ({ datasetId, datasetName, indexingType, creationCache }: Step
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn(s.sideTip)}>
|
||||
{!isMobile && <div className={cn(s.sideTip)}>
|
||||
<div className={s.tipCard}>
|
||||
<span className={s.icon}/>
|
||||
<div className={s.title}>{t('datasetCreation.stepThree.sideTipTitle')}</div>
|
||||
<div className={s.content}>{t('datasetCreation.stepThree.sideTipContent')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
.pageHeader {
|
||||
@apply px-16;
|
||||
@apply px-16 flex justify-between items-center;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -251,7 +251,7 @@
|
||||
}
|
||||
|
||||
.ruleItem {
|
||||
@apply flex items-center h-7;
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.formFooter {
|
||||
@ -382,7 +382,7 @@
|
||||
|
||||
.previewWrap {
|
||||
flex-shrink: 0;
|
||||
width: 524px;
|
||||
max-width: 524px;
|
||||
}
|
||||
|
||||
.previewHeader {
|
||||
|
@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { RocketLaunchIcon } from '@heroicons/react/24/outline'
|
||||
import cn from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { groupBy } from 'lodash-es'
|
||||
@ -20,6 +21,7 @@ import {
|
||||
} from '@/service/datasets'
|
||||
import Button from '@/app/components/base/button'
|
||||
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 EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||
import { type RetrievalConfig } from '@/types/app'
|
||||
@ -37,6 +39,8 @@ import I18n from '@/context/i18n'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import { RETRIEVE_METHOD } from '@/types/app'
|
||||
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 StepTwoProps = {
|
||||
@ -84,6 +88,9 @@ const StepTwo = ({
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const { dataset: currentDataset, mutateDatasetRes } = useDatasetDetailContext()
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
@ -467,7 +474,7 @@ const StepTwo = ({
|
||||
useEffect(() => {
|
||||
if (segmentationType === SegmentType.AUTO) {
|
||||
setAutomaticFileIndexingEstimate(null)
|
||||
setShowPreview()
|
||||
!isMobile && setShowPreview()
|
||||
fetchFileIndexingEstimate()
|
||||
setPreviewSwitched(false)
|
||||
}
|
||||
@ -493,8 +500,23 @@ const StepTwo = ({
|
||||
return (
|
||||
<div className='flex w-full h-full'>
|
||||
<div ref={scrollRef} className='relative h-full w-full overflow-y-scroll'>
|
||||
<div className={cn(s.pageHeader, scrolled && s.fixed)}>{t('datasetCreation.steps.two')}</div>
|
||||
<div className={cn(s.form)}>
|
||||
<div className={cn(s.pageHeader, scrolled && s.fixed, isMobile && '!px-6')}>
|
||||
<span>{t('datasetCreation.steps.two')}</span>
|
||||
{isMobile && (
|
||||
<Button
|
||||
className='border-[0.5px] !h-8 hover:outline hover:outline-[0.5px] hover:outline-gray-300 text-gray-700 font-medium bg-white shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)]'
|
||||
onClick={setShowPreview}
|
||||
>
|
||||
<Tooltip selector='data-preview-toggle'>
|
||||
<div className="flex flex-row items-center">
|
||||
<RocketLaunchIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" />
|
||||
<span className="text-[13px]">{t('datasetCreation.stepTwo.previewTitleButton')}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={cn(s.form, isMobile && '!px-4')}>
|
||||
<div className={s.label}>{t('datasetCreation.stepTwo.segmentation')}</div>
|
||||
<div className='max-w-[640px]'>
|
||||
<div
|
||||
@ -554,7 +576,7 @@ const StepTwo = ({
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
{rules.map(rule => (
|
||||
<div key={rule.id} className={s.ruleItem}>
|
||||
@ -574,7 +596,7 @@ const StepTwo = ({
|
||||
</div>
|
||||
<div className={s.label}>{t('datasetCreation.stepTwo.indexMode')}</div>
|
||||
<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)) && (
|
||||
<div
|
||||
className={cn(
|
||||
@ -797,9 +819,8 @@ const StepTwo = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(showPreview)
|
||||
? (
|
||||
<div ref={previewScrollRef} className={cn(s.previewWrap, 'relativeh-full overflow-y-scroll border-l border-[#F2F4F7]')}>
|
||||
<FloatRightContainer isMobile={isMobile} isOpen={showPreview} onClose={hidePreview} footer={null}>
|
||||
{showPreview && <div ref={previewScrollRef} className={cn(s.previewWrap, 'relative h-full overflow-y-scroll border-l border-[#F2F4F7]')}>
|
||||
<div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`)}>
|
||||
<div className='flex items-center justify-between px-8'>
|
||||
<div className='grow flex items-center'>
|
||||
@ -845,9 +866,9 @@ const StepTwo = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: (<div className={cn(s.sideTip)}>
|
||||
</div>}
|
||||
{!showPreview && (
|
||||
<div className={cn(s.sideTip)}>
|
||||
<div className={s.tipCard}>
|
||||
<span className={s.icon} />
|
||||
<div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div>
|
||||
@ -858,7 +879,9 @@ const StepTwo = ({
|
||||
<p>{t('datasetCreation.stepTwo.sideTipP4')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>)}
|
||||
</div>
|
||||
)}
|
||||
</FloatRightContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -14,14 +14,15 @@
|
||||
background-size: 16px;
|
||||
}
|
||||
.stepList {
|
||||
@apply p-4;
|
||||
@apply p-4 relative;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.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;
|
||||
font-size: 13px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.stepItem.step1::before {
|
||||
|
@ -3,46 +3,56 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
import cn from 'classnames'
|
||||
import { useCallback } from 'react'
|
||||
import s from './index.module.css'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
type IStepsNavBarProps = {
|
||||
step: number,
|
||||
datasetId?: string,
|
||||
step: number
|
||||
datasetId?: string
|
||||
}
|
||||
|
||||
const STEP_T_MAP: Record<number, string> = {
|
||||
1: 'datasetCreation.steps.one',
|
||||
2: 'datasetCreation.steps.two',
|
||||
3: 'datasetCreation.steps.three',
|
||||
}
|
||||
|
||||
const STEP_LIST = [1, 2, 3]
|
||||
|
||||
const StepsNavBar = ({
|
||||
step,
|
||||
datasetId,
|
||||
}: IStepsNavBarProps) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const navBackHandle = () => {
|
||||
if (!datasetId) {
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const navBackHandle = useCallback(() => {
|
||||
if (!datasetId)
|
||||
router.replace('/datasets')
|
||||
} else {
|
||||
else
|
||||
router.replace(`/datasets/${datasetId}/documents`)
|
||||
}
|
||||
}
|
||||
}, [router, datasetId])
|
||||
|
||||
return (
|
||||
<div className='w-full pt-4'>
|
||||
<div className={s.stepsHeader}>
|
||||
<div onClick={navBackHandle} className={s.navBack} />
|
||||
{!datasetId ? t('datasetCreation.steps.header.creation') : t('datasetCreation.steps.header.update')}
|
||||
<div className={cn(s.stepsHeader, isMobile && '!px-0 justify-center')}>
|
||||
<div onClick={navBackHandle} className={cn(s.navBack, isMobile && '!mr-0')} />
|
||||
{!isMobile && (!datasetId ? t('datasetCreation.steps.header.creation') : t('datasetCreation.steps.header.update'))}
|
||||
</div>
|
||||
<div className={cn(s.stepList)}>
|
||||
<div className={cn(s.stepItem, s.step1, step === 1 && s.active, step !== 1 && s.done)}>
|
||||
<div className={cn(s.stepNum)}>{step === 1 ? 1 : ''}</div>
|
||||
<div className={cn(s.stepName)}>{t('datasetCreation.steps.one')}</div>
|
||||
</div>
|
||||
<div className={cn(s.stepItem, s.step2, step === 2 && s.active, step === 3 && s.done)}>
|
||||
<div className={cn(s.stepNum)}>{step !== 3 ? 2 : ''}</div>
|
||||
<div className={cn(s.stepName)}>{t('datasetCreation.steps.two')}</div>
|
||||
</div>
|
||||
<div className={cn(s.stepItem, s.step3, step === 3 && s.active)}>
|
||||
<div className={cn(s.stepNum)}>3</div>
|
||||
<div className={cn(s.stepName)}>{t('datasetCreation.steps.three')}</div>
|
||||
<div className={cn(s.stepList, isMobile && '!p-0')}>
|
||||
{STEP_LIST.map(item => (
|
||||
<div
|
||||
key={item}
|
||||
className={cn(s.stepItem, s[`step${item}`], step === item && s.active, step > item && s.done, isMobile && 'px-0')}
|
||||
>
|
||||
<div className={cn(s.stepNum)}>{item}</div>
|
||||
<div className={cn(s.stepName)}>{isMobile ? '' : t(STEP_T_MAP[item])}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -178,7 +178,7 @@ const SegmentDetailComponent: FC<ISegmentDetailProps> = ({
|
||||
}
|
||||
</div>
|
||||
<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.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>
|
||||
|
@ -8,7 +8,7 @@
|
||||
@apply text-gray-900 font-medium text-base flex-1;
|
||||
}
|
||||
.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 {
|
||||
height: calc(100% - 3.25rem);
|
||||
@ -18,7 +18,7 @@
|
||||
@apply grid gap-4 grid-cols-3 min-w-[902px] last:mb-[30px];
|
||||
}
|
||||
.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 {
|
||||
@apply flex items-center justify-between;
|
||||
@ -48,7 +48,7 @@
|
||||
white-space: pre-line;
|
||||
}
|
||||
.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 {
|
||||
@apply text-gray-500 text-xs font-medium;
|
||||
|
@ -23,6 +23,8 @@ import { checkSegmentBatchImportProgress, fetchDocumentDetail, segmentBatchImpor
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import type { DocForm } from '@/models/datasets'
|
||||
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: '' })
|
||||
|
||||
@ -50,10 +52,14 @@ type Props = {
|
||||
const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { dataset } = useDatasetDetailContext()
|
||||
const embeddingAvailable = !!dataset?.embedding_available
|
||||
const [showMetadata, setShowMetadata] = useState(true)
|
||||
const [showMetadata, setShowMetadata] = useState(!isMobile)
|
||||
const [newSegmentModalVisible, setNewSegmentModalVisible] = useState(false)
|
||||
const [batchModalVisible, setBatchModalVisible] = useState(false)
|
||||
const [importStatus, setImportStatus] = useState<ProcessStatus | string>()
|
||||
@ -124,12 +130,13 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
|
||||
return (
|
||||
<DocumentContext.Provider value={{ datasetId, documentId, docForm: documentDetail?.doc_form || '' }}>
|
||||
<div className='flex flex-col h-full'>
|
||||
<div className='flex h-16 border-b-gray-100 border-b items-center p-4'>
|
||||
<div onClick={backToPrev} className={'rounded-full w-8 h-8 flex justify-center items-center border-gray-100 cursor-pointer border hover:border-gray-300 shadow-[0px_12px_16px_-4px_rgba(16,24,40,0.08),0px_4px_6px_-2px_rgba(16,24,40,0.03)]'}>
|
||||
<div className='flex min-h-16 border-b-gray-100 border-b items-center p-4 justify-between flex-wrap gap-y-2'>
|
||||
<div onClick={backToPrev} className={'shrink-0 rounded-full w-8 h-8 flex justify-center items-center border-gray-100 cursor-pointer border hover:border-gray-300 shadow-[0px_12px_16px_-4px_rgba(16,24,40,0.08),0px_4px_6px_-2px_rgba(16,24,40,0.03)]'}>
|
||||
<ArrowLeftIcon className='text-primary-600 fill-current stroke-current h-4 w-4' />
|
||||
</div>
|
||||
<Divider className='!h-4' type='vertical' />
|
||||
<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 || ''} />
|
||||
{embeddingAvailable && documentDetail && !documentDetail.archived && (
|
||||
<SegmentAdd
|
||||
@ -158,10 +165,11 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
|
||||
onClick={() => setShowMetadata(!showMetadata)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}>
|
||||
{isDetailLoading
|
||||
? <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 detail={documentDetail} detailUpdate={detailMutate} />
|
||||
: <Completed
|
||||
@ -174,11 +182,13 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
|
||||
}
|
||||
</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}
|
||||
loading={isMetadataLoading}
|
||||
onUpdate={metadataMutate}
|
||||
/>}
|
||||
/>
|
||||
</FloatRightContainer>
|
||||
</div>
|
||||
<BatchModal
|
||||
isShow={batchModalVisible}
|
||||
|
@ -1,5 +1,5 @@
|
||||
.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 {
|
||||
@apply flex flex-col items-center gap-4 mt-7 mb-8;
|
||||
|
@ -198,7 +198,7 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
|
||||
<p className={s.desc}>{t('datasetDocuments.list.desc')}</p>
|
||||
</div>
|
||||
<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
|
||||
showPrefix
|
||||
wrapperClassName='!w-[200px]'
|
||||
@ -207,7 +207,7 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
|
||||
value={searchValue}
|
||||
/>
|
||||
{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' />
|
||||
{isDataSourceNotion && t('datasetDocuments.list.addPages')}
|
||||
{!isDataSourceNotion && t('datasetDocuments.list.addFile')}
|
||||
|
@ -316,8 +316,8 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<table className={`w-full border-collapse border-0 text-sm mt-3 ${s.documentTable}`}>
|
||||
<div className='w-full h-full overflow-x-auto'>
|
||||
<table className={`min-w-[700px] max-w-full w-full border-collapse border-0 text-sm mt-3 ${s.documentTable}`}>
|
||||
<thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-medium text-xs uppercase">
|
||||
<tr>
|
||||
<td className='w-12'>#</td>
|
||||
@ -380,7 +380,7 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,8 @@ const HitDetail: FC<IHitDetailProps> = ({ segInfo, vectorInfo }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'flex flex-row'}>
|
||||
<div className="flex-1 bg-gray-25 p-6">
|
||||
<div className='flex flex-row overflow-x-auto'>
|
||||
<div className="flex-1 bg-gray-25 p-6 min-w-[300px]">
|
||||
<div className="flex items-center">
|
||||
<SegmentIndexTag
|
||||
positionId={segInfo?.position || ''}
|
||||
|
@ -1,11 +1,12 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { omit } from 'lodash-es'
|
||||
import cn from 'classnames'
|
||||
import dayjs from 'dayjs'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import SegmentCard from '../documents/detail/completed/SegmentCard'
|
||||
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 Modal from '@/app/components/base/modal'
|
||||
import Pagination from '@/app/components/base/pagination'
|
||||
import FloatRightContainer from '@/app/components/base/float-right-container'
|
||||
import { fetchTestingRecords } from '@/service/datasets'
|
||||
import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
const limit = 10
|
||||
|
||||
@ -39,6 +42,10 @@ const RecordsEmpty: FC = () => {
|
||||
|
||||
const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const [hitResult, setHitResult] = useState<HitTestingResponse | undefined>() // 初始化记录为空数组
|
||||
const [submitLoading, setSubmitLoading] = useState(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 [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false)
|
||||
const [isShowRightPanel, { setTrue: showRightPanel, setFalse: hideRightPanel, set: setShowRightPanel }] = useBoolean(!isMobile)
|
||||
|
||||
useEffect(() => {
|
||||
setShowRightPanel(!isMobile)
|
||||
}, [isMobile, setShowRightPanel])
|
||||
|
||||
return (
|
||||
<div className={s.container}>
|
||||
@ -74,6 +86,7 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||
<Textarea
|
||||
datasetId={datasetId}
|
||||
setHitResult={setHitResult}
|
||||
onSubmit={showRightPanel}
|
||||
onUpdateList={recordsMutate}
|
||||
loading={submitLoading}
|
||||
setLoading={setSubmitLoading}
|
||||
@ -131,7 +144,8 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||
<RecordsEmpty />
|
||||
)}
|
||||
</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
|
||||
? <div className={s.cardWrapper}>
|
||||
<SegmentCard
|
||||
@ -176,8 +190,10 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FloatRightContainer>
|
||||
<Modal
|
||||
className='!max-w-[960px] !p-0'
|
||||
wrapperClassName='!z-40'
|
||||
closable
|
||||
onClose={() => setCurrParagraph({ showModal: false })}
|
||||
isShow={currParagraph.showModal}
|
||||
|
@ -1,5 +1,5 @@
|
||||
.container {
|
||||
@apply flex h-full w-full relative;
|
||||
@apply flex h-full w-full relative overflow-y-auto;
|
||||
}
|
||||
.container > div {
|
||||
@apply flex-1 h-full;
|
||||
@ -8,7 +8,7 @@
|
||||
@apply border-r border-gray-100 px-6 py-3 flex flex-col;
|
||||
}
|
||||
.rightDiv {
|
||||
@apply px-8 pt-[42px] pb-[26px] flex flex-col;
|
||||
@apply flex flex-col;
|
||||
}
|
||||
.titleWrapper {
|
||||
@apply flex flex-col justify-center gap-1 mb-5;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import type { FC } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
@ -13,7 +12,7 @@ import { hitTesting } from '@/service/datasets'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
|
||||
|
||||
type Props = {
|
||||
type TextAreaWithButtonIProps = {
|
||||
datasetId: string
|
||||
onUpdateList: () => void
|
||||
setHitResult: (res: HitTestingResponse) => void
|
||||
@ -24,9 +23,10 @@ type Props = {
|
||||
onClickRetrievalMethod: () => void
|
||||
retrievalConfig: RetrievalConfig
|
||||
isEconomy: boolean
|
||||
onSubmit?: () => void
|
||||
}
|
||||
|
||||
const TextAreaWithButton: FC<Props> = ({
|
||||
const TextAreaWithButton = ({
|
||||
datasetId,
|
||||
onUpdateList,
|
||||
setHitResult,
|
||||
@ -37,7 +37,8 @@ const TextAreaWithButton: FC<Props> = ({
|
||||
onClickRetrievalMethod,
|
||||
retrievalConfig,
|
||||
isEconomy,
|
||||
}) => {
|
||||
onSubmit: _onSubmit,
|
||||
}: TextAreaWithButtonIProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { indexingTechnique } = useContext(DatasetDetailContext)
|
||||
|
||||
@ -55,6 +56,7 @@ const TextAreaWithButton: FC<Props> = ({
|
||||
onUpdateList?.()
|
||||
}
|
||||
setLoading(false)
|
||||
_onSubmit && _onSubmit()
|
||||
}
|
||||
|
||||
const retrievalMethod = isEconomy ? RETRIEVE_METHOD.invertedIndex : retrievalConfig.search_method
|
||||
|
@ -24,13 +24,13 @@ import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||
const rowClass = `
|
||||
flex justify-between py-4
|
||||
flex justify-between py-4 flex-wrap gap-y-2
|
||||
`
|
||||
const labelClass = `
|
||||
flex items-center w-[168px] h-9
|
||||
`
|
||||
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) => {
|
||||
useEffect(() => {
|
||||
@ -118,14 +118,14 @@ const Form = () => {
|
||||
useInitialValue<DataSet['indexing_technique'] | undefined>(currentDataset?.indexing_technique, setIndexMethod)
|
||||
|
||||
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={labelClass}>
|
||||
<div>{t('datasetSettings.form.name')}</div>
|
||||
</div>
|
||||
<input
|
||||
disabled={!currentDataset?.embedding_available}
|
||||
className={cn(inputClass, !currentDataset?.embedding_available && 'opacity-60')}
|
||||
className={cn(inputClass, !currentDataset?.embedding_available && 'opacity-60', 'h-9')}
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
/>
|
||||
@ -134,7 +134,7 @@ const Form = () => {
|
||||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.desc')}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<textarea
|
||||
disabled={!currentDataset?.embedding_available}
|
||||
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>{t('datasetSettings.form.permissions')}</div>
|
||||
</div>
|
||||
<div className='w-[480px]'>
|
||||
<div className='w-full sm:w-[480px]'>
|
||||
<PermissionsRadio
|
||||
disable={!currentDataset?.embedding_available}
|
||||
value={permission}
|
||||
@ -167,7 +167,7 @@ const Form = () => {
|
||||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.indexMethod')}</div>
|
||||
</div>
|
||||
<div className='w-[480px]'>
|
||||
<div className='w-full sm:w-[480px]'>
|
||||
<IndexMethodRadio
|
||||
disable={!currentDataset?.embedding_available}
|
||||
value={indexMethod}
|
||||
|
@ -5,7 +5,7 @@ import s from './index.module.css'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
|
||||
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 = `
|
||||
w-4 h-4 border-[2px] border-gray-200 rounded-full
|
||||
@ -40,7 +40,7 @@ const IndexMethodRadio = ({
|
||||
]
|
||||
|
||||
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 => (
|
||||
<div
|
||||
|
@ -5,7 +5,7 @@ import s from './index.module.css'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
|
||||
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 = `
|
||||
w-4 h-4 border-[2px] border-gray-200 rounded-full
|
||||
@ -36,7 +36,7 @@ const PermissionsRadio = ({
|
||||
]
|
||||
|
||||
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 => (
|
||||
<div
|
||||
|
@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import s from './secret-key/style.module.css'
|
||||
import Doc from '@/app/components/develop/doc'
|
||||
import InputCopy from '@/app/components/develop/secret-key/input-copy'
|
||||
import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button'
|
||||
import { fetchAppDetail } from '@/service/apps'
|
||||
import s from './secret-key/style.module.css'
|
||||
|
||||
type IDevelopMainProps = {
|
||||
appId: string
|
||||
@ -21,10 +21,10 @@ const DevelopMain = ({ appId, dictionary }: IDevelopMainProps) => {
|
||||
|
||||
return (
|
||||
<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='flex items-center'>
|
||||
<InputCopy className={`flex-shrink-0 mr-1 w-60 ${s.w320}`} value={appDetail?.api_base_url}>
|
||||
<div className='flex items-center flex-wrap gap-y-1'>
|
||||
<InputCopy className='flex-shrink-0 mr-1 w-52 sm:w-80' value={appDetail?.api_base_url}>
|
||||
<div className={`ml-2 border border-gray-200 border-solid flex-shrink-0 px-2 py-0.5 rounded-[6px] text-gray-500 text-[0.625rem] ${s.customApi}`}>
|
||||
{t('appApi.apiServer')}
|
||||
</div>
|
||||
@ -37,7 +37,7 @@ const DevelopMain = ({ appId, dictionary }: IDevelopMainProps) => {
|
||||
<SecretKeyButton className='flex-shrink-0' appId={appId} />
|
||||
</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} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,10 +16,6 @@
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.w320 {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.customApi {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ const InstalledApp: FC<IInstalledAppProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full p-2'>
|
||||
<div className='h-full py-2 pl-0 pr-2 sm:p-2'>
|
||||
{installedApp?.app.mode === 'chat'
|
||||
? (
|
||||
<ChatApp isInstalledApp installedAppInfo={installedApp} />
|
||||
|
@ -9,6 +9,7 @@ import ItemOperation from '@/app/components/explore/item-operation'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
|
||||
export type IAppNavItemProps = {
|
||||
isMobile: boolean
|
||||
name: string
|
||||
id: string
|
||||
icon: string
|
||||
@ -21,6 +22,7 @@ export type IAppNavItemProps = {
|
||||
}
|
||||
|
||||
export default function AppNavItem({
|
||||
isMobile,
|
||||
name,
|
||||
id,
|
||||
icon,
|
||||
@ -42,17 +44,19 @@ export default function AppNavItem({
|
||||
className={cn(
|
||||
s.item,
|
||||
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={() => {
|
||||
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'>
|
||||
<AppIcon size='tiny' icon={icon} background={icon_background} />
|
||||
<div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div>
|
||||
</div>
|
||||
{
|
||||
<div className='shrink-0 h-6' onClick={e => e.stopPropagation()}>
|
||||
<ItemOperation
|
||||
isPinned={isPinned}
|
||||
@ -62,7 +66,8 @@ export default function AppNavItem({
|
||||
onDelete={() => onDelete(id)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import Item from './app-nav-item'
|
||||
import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp, updatePinStatus } from '@/service/explore'
|
||||
import ExploreContext from '@/context/explore-context'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
const SelectedDiscoveryIcon = () => (
|
||||
<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>
|
||||
)
|
||||
|
||||
const SideBar: FC<{
|
||||
export type IExploreSideBarProps = {
|
||||
controlUpdateInstalledApps: number
|
||||
}> = ({
|
||||
}
|
||||
|
||||
const SideBar: FC<IExploreSideBarProps> = ({
|
||||
controlUpdateInstalledApps,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@ -48,6 +51,9 @@ const SideBar: FC<{
|
||||
const isChatSelected = lastSegment === 'chat'
|
||||
const { installedApps, setInstalledApps } = useContext(ExploreContext)
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const fetchInstalledAppList = async () => {
|
||||
const { installed_apps }: any = await doFetchInstalledAppList()
|
||||
setInstalledApps(installed_apps)
|
||||
@ -84,28 +90,28 @@ const SideBar: FC<{
|
||||
}, [controlUpdateInstalledApps])
|
||||
|
||||
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>
|
||||
<Link
|
||||
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)' } : {}}
|
||||
>
|
||||
{isDiscoverySelected ? <SelectedDiscoveryIcon /> : <DiscoveryIcon />}
|
||||
<div className='text-sm'>{t('explore.sidebar.discovery')}</div>
|
||||
{!isMobile && <div className='text-sm'>{t('explore.sidebar.discovery')}</div>}
|
||||
</Link>
|
||||
<Link
|
||||
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)' } : {}}
|
||||
>
|
||||
{isChatSelected ? <SelectedChatIcon /> : <ChatIcon />}
|
||||
<div className='text-sm'>{t('explore.sidebar.chat')}</div>
|
||||
{!isMobile && <div className='text-sm'>{t('explore.sidebar.chat')}</div>}
|
||||
</Link>
|
||||
</div>
|
||||
{installedApps.length > 0 && (
|
||||
<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'
|
||||
style={{
|
||||
height: 'calc(100vh - 250px)',
|
||||
@ -115,6 +121,7 @@ const SideBar: FC<{
|
||||
return (
|
||||
<Item
|
||||
key={id}
|
||||
isMobile={isMobile}
|
||||
name={name}
|
||||
icon={icon}
|
||||
icon_background={icon_background}
|
||||
|
@ -10,7 +10,6 @@ import produce from 'immer'
|
||||
import { useBoolean, useGetState } from 'ahooks'
|
||||
import AppUnavailable from '../../base/app-unavailable'
|
||||
import useConversation from './hooks/use-conversation'
|
||||
import s from './style.module.css'
|
||||
import Init from './init'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import Sidebar from '@/app/components/share/chat/sidebar'
|
||||
@ -721,10 +720,10 @@ const Main: FC<IMainProps> = () => {
|
||||
return <Loading type='app' />
|
||||
|
||||
return (
|
||||
<div className='bg-gray-100'>
|
||||
<div className='bg-gray-100 h-full'>
|
||||
<div
|
||||
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={{
|
||||
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 */}
|
||||
<div className={cn(
|
||||
s.installedApp,
|
||||
'flex-grow flex flex-col overflow-y-auto',
|
||||
'h-full flex-grow flex flex-col overflow-y-auto',
|
||||
)
|
||||
}>
|
||||
{(!isNewConversation || isResponsing || errorHappened) && (
|
||||
|
@ -8,7 +8,7 @@ import Config from '../config'
|
||||
import s from './style.module.css'
|
||||
|
||||
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)"/>
|
||||
<defs>
|
||||
<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()
|
||||
|
||||
return (
|
||||
<div className='h-full flex items-center'>
|
||||
<div className='h-full flex items-center justify-center'>
|
||||
<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='mb-2 font-normal text-sm text-gray-500'>{t('explore.universalChat.welcomeDescribe')}</div>
|
||||
</div>
|
||||
<div className='flex mb-2 mx-auto h-8 items-center'>
|
||||
<div className='flex mb-2 h-8 items-center'>
|
||||
{Line}
|
||||
</div>
|
||||
<Config className='w-[480px] mx-auto' {...configProps} />
|
||||
<Config {...configProps} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,3 +0,0 @@
|
||||
.installedApp {
|
||||
height: calc(100vh - 74px);
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
import classNames from 'classnames'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import s from './index.module.css'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
|
||||
type HeaderWrapperProps = {
|
||||
children: React.ReactNode
|
||||
@ -12,23 +11,17 @@ const HeaderWrapper = ({
|
||||
children,
|
||||
}: HeaderWrapperProps) => {
|
||||
const pathname = usePathname()
|
||||
const { langeniusVersionInfo } = useAppContext()
|
||||
const isBordered = ['/apps', '/datasets'].includes(pathname)
|
||||
|
||||
return (
|
||||
<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,
|
||||
isBordered ? 'border-b border-gray-200' : '',
|
||||
)}
|
||||
>
|
||||
<div className={classNames(
|
||||
s[`header-${langeniusVersionInfo.current_env}`],
|
||||
'flex flex-1 items-center justify-between px-4',
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default HeaderWrapper
|
||||
|
@ -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 { useModalContext } from '@/context/modal-context'
|
||||
|
||||
export default function AppSelector() {
|
||||
export type IAppSelecotr = {
|
||||
isMobile: boolean
|
||||
}
|
||||
|
||||
export default function AppSelector({ isMobile }: IAppSelecotr) {
|
||||
const itemClassName = `
|
||||
flex items-center w-full h-9 px-3 text-gray-700 text-[14px]
|
||||
rounded-lg font-normal hover:bg-gray-50 cursor-pointer
|
||||
@ -50,12 +54,15 @@ export default function AppSelector() {
|
||||
inline-flex items-center
|
||||
rounded-[20px] py-1 pr-2.5 pl-1 text-sm
|
||||
text-gray-700 hover:bg-gray-200
|
||||
mobile:px-1
|
||||
${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}
|
||||
<ChevronDown className="w-3 h-3 ml-1 text-gray-700"/>
|
||||
</>}
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
|
@ -70,7 +70,7 @@ const ApiBasedExtensionSelector: FC<ApiBasedExtensionSelectorProps> = ({
|
||||
)
|
||||
}
|
||||
</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='p-1'>
|
||||
<div className='flex items-center justify-between px-3 pt-2 pb-1'>
|
||||
|
@ -76,7 +76,7 @@ const DataSourceNotion = ({
|
||||
: (
|
||||
<div
|
||||
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'}`
|
||||
}
|
||||
onClick={handleConnectNotion}
|
||||
|
@ -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 { AtSign, XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
const iconClassName = `
|
||||
w-4 h-4 ml-3 mr-2
|
||||
@ -42,6 +43,10 @@ export default function AccountSetting({
|
||||
}: IAccountSettingProps) {
|
||||
const [activeMenu, setActiveMenu] = useState(activeTab)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
key: 'workspace-group',
|
||||
@ -130,9 +135,9 @@ export default function AccountSetting({
|
||||
wrapperClassName='!z-20 pt-[60px]'
|
||||
>
|
||||
<div className='flex'>
|
||||
<div className='w-[200px] p-4 border border-gray-100'>
|
||||
<div className='mb-8 ml-2 text-base font-medium leading-6 text-gray-900'>{t('common.userProfile.settings')}</div>
|
||||
<div>
|
||||
<div className='w-[44px] sm:w-[200px] px-[1px] py-4 sm:p-4 border border-gray-100 shrink-0 sm:shrink-1 flex flex-col items-center sm:items-start'>
|
||||
<div className='mb-8 ml-0 sm:ml-2 text-sm sm:text-base font-medium leading-6 text-gray-900'>{t('common.userProfile.settings')}</div>
|
||||
<div className='w-full'>
|
||||
{
|
||||
menuItems.map(menuItem => (
|
||||
<div key={menuItem.key} className='mb-4'>
|
||||
@ -150,7 +155,7 @@ export default function AccountSetting({
|
||||
onClick={() => setActiveMenu(item.key)}
|
||||
>
|
||||
{activeMenu === item.key ? item.activeIcon : item.icon}
|
||||
<div className='truncate'>{item.name}</div>
|
||||
{!isMobile && <div className='truncate'>{item.name}</div>}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
@ -167,7 +172,7 @@ export default function AccountSetting({
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-8 pt-2'>
|
||||
<div className='px-4 sm:px-8 pt-2'>
|
||||
{activeMenu === 'account' && <AccountPage />}
|
||||
{activeMenu === 'members' && <MembersPage />}
|
||||
{activeMenu === 'integrations' && <IntegrationsPage />}
|
||||
|
@ -51,13 +51,13 @@ const MembersPage = () => {
|
||||
{t('common.members.invite')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='flex items-center py-[7px] border-b border-gray-200'>
|
||||
<div className='overflow-x-auto'>
|
||||
<div className='flex items-center py-[7px] border-b border-gray-200 min-w-[480px]'>
|
||||
<div className='grow px-3 text-xs font-medium text-gray-500'>{t('common.members.name')}</div>
|
||||
<div className='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>
|
||||
<div>
|
||||
<div className='min-w-[480px]'>
|
||||
{
|
||||
accounts.map(account => (
|
||||
<div key={account.id} className='flex border-b border-gray-100'>
|
||||
|
@ -218,7 +218,7 @@ const ModelPage = () => {
|
||||
}
|
||||
<SystemModel onUpdate={() => mutateProviders()} />
|
||||
</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) => (
|
||||
<ModelCard
|
||||
|
@ -34,7 +34,7 @@ const ModelItem: FC<ModelItemProps> = ({
|
||||
|
||||
return (
|
||||
<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'>
|
||||
{modelItem.titleIcon[locale]}
|
||||
{
|
||||
|
@ -159,7 +159,7 @@ const Form: FC<FormProps> = ({
|
||||
options?.map(option => (
|
||||
<div
|
||||
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'}
|
||||
`}
|
||||
onClick={() => handleFormChange(field.key, option.key)}
|
||||
|
@ -96,7 +96,7 @@ const ModelModal: FC<ModelModalProps> = ({
|
||||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent className='w-full h-full z-[60]'>
|
||||
<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='flex justify-between items-center mb-2'>
|
||||
<div className='text-xl font-semibold text-gray-900'>{renderTitlePrefix()}</div>
|
||||
@ -113,7 +113,7 @@ const ModelModal: FC<ModelModalProps> = ({
|
||||
onClearedChange={setCleared}
|
||||
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
|
||||
href={modelModal?.link.href}
|
||||
target='_blank'
|
||||
|
@ -1,43 +1,90 @@
|
||||
'use client'
|
||||
|
||||
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 AppNav from './app-nav'
|
||||
import DatasetNav from './dataset-nav'
|
||||
import EnvNav from './env-nav'
|
||||
import ExploreNav from './explore-nav'
|
||||
import GithubStar from './github-star'
|
||||
import s from './index.module.css'
|
||||
import { WorkspaceProvider } from '@/context/workspace-context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import LogoSite from '@/app/components/base/logo/logo-site'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
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
|
||||
cursor-pointer
|
||||
`
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className={classNames(
|
||||
s[`header-${langeniusVersionInfo.current_env}`],
|
||||
'flex flex-1 items-center justify-between px-4',
|
||||
)}>
|
||||
<div className='flex items-center'>
|
||||
{isMobile && <div
|
||||
className='flex items-center justify-center h-8 w-8 cursor-pointer'
|
||||
onClick={toggle}
|
||||
>
|
||||
<Bars3Icon className="h-4 w-4 text-gray-500" />
|
||||
</div>}
|
||||
{!isMobile && <>
|
||||
<Link href="/apps" className='flex items-center mr-4'>
|
||||
<LogoSite />
|
||||
</Link>
|
||||
<GithubStar />
|
||||
</>}
|
||||
</div>
|
||||
{isMobile && (
|
||||
<div className='flex'>
|
||||
<Link href="/apps" className='flex items-center mr-4'>
|
||||
<LogoSite />
|
||||
</Link>
|
||||
<GithubStar />
|
||||
</div>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<div className='flex items-center'>
|
||||
<ExploreNav className={navClassName} />
|
||||
<AppNav />
|
||||
{isCurrentWorkspaceManager && <DatasetNav />}
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center flex-shrink-0'>
|
||||
<EnvNav />
|
||||
<WorkspaceProvider>
|
||||
<AccountDropdown />
|
||||
<AccountDropdown isMobile={isMobile} />
|
||||
</WorkspaceProvider>
|
||||
</div>
|
||||
</div>
|
||||
{(isMobile && isShowNavMenu) && (
|
||||
<div className='w-full flex flex-col p-2 gap-y-1'>
|
||||
<ExploreNav className={navClassName} />
|
||||
<AppNav />
|
||||
{isCurrentWorkspaceManager && <DatasetNav />}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ const Nav = ({
|
||||
|
||||
return (
|
||||
<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'}
|
||||
${!curNav && !isActived && 'hover:bg-gray-200'}
|
||||
`}>
|
||||
|
@ -11,7 +11,6 @@ import { useBoolean, useGetState } from 'ahooks'
|
||||
import AppUnavailable from '../../base/app-unavailable'
|
||||
import { checkOrSetAccessToken } from '../utils'
|
||||
import useConversation from './hooks/use-conversation'
|
||||
import s from './style.module.css'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import Sidebar from '@/app/components/share/chat/sidebar'
|
||||
import ConfigSence from '@/app/components/share/chat/config-scence'
|
||||
@ -683,7 +682,7 @@ const Main: FC<IMainProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='bg-gray-100'>
|
||||
<div className='bg-gray-100 h-full'>
|
||||
{!isInstalledApp && (
|
||||
<Header
|
||||
title={siteInfo.title}
|
||||
@ -720,7 +719,7 @@ const Main: FC<IMainProps> = ({
|
||||
)}
|
||||
{/* main */}
|
||||
<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',
|
||||
)
|
||||
}>
|
||||
|
@ -1,3 +0,0 @@
|
||||
.installedApp {
|
||||
height: calc(100vh - 74px);
|
||||
}
|
@ -10,7 +10,6 @@ import { useBoolean, useGetState } from 'ahooks'
|
||||
import { checkOrSetAccessToken } from '../utils'
|
||||
import AppUnavailable from '../../base/app-unavailable'
|
||||
import useConversation from './hooks/use-conversation'
|
||||
import s from './style.module.css'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import ConfigScene from '@/app/components/share/chatbot/config-scence'
|
||||
import Header from '@/app/components/share/header'
|
||||
@ -561,7 +560,7 @@ const Main: FC<IMainProps> = ({
|
||||
|
||||
<div className={'flex bg-white overflow-hidden'}>
|
||||
<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',
|
||||
)
|
||||
}>
|
||||
|
@ -1,3 +0,0 @@
|
||||
.installedApp {
|
||||
height: calc(100vh - 74px);
|
||||
}
|
@ -33,7 +33,7 @@ const Header: FC<IHeaderProps> = ({
|
||||
return (
|
||||
<div
|
||||
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
|
||||
`}
|
||||
>
|
||||
@ -52,7 +52,7 @@ const Header: FC<IHeaderProps> = ({
|
||||
}
|
||||
|
||||
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
|
||||
className='flex items-center justify-center h-8 w-8 cursor-pointer'
|
||||
onClick={() => onShowSideBar?.()}
|
||||
|
@ -1,5 +1,5 @@
|
||||
.installedApp {
|
||||
height: calc(100vh - 74px);
|
||||
height: 100%;
|
||||
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);
|
||||
}
|
||||
|
@ -120,9 +120,9 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
|
||||
isCurrentWorkspaceManager,
|
||||
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 />}
|
||||
<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}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,6 +21,7 @@ const translation = {
|
||||
operation: {
|
||||
applyConfig: 'Publish',
|
||||
resetConfig: 'Reset',
|
||||
debugConfig: 'Debug',
|
||||
addFeature: 'Add Feature',
|
||||
automatic: 'Automatic',
|
||||
stopResponding: 'Stop responding',
|
||||
|
@ -21,6 +21,7 @@ const translation = {
|
||||
operation: {
|
||||
applyConfig: '发布',
|
||||
resetConfig: '重置',
|
||||
debugConfig: '调试',
|
||||
addFeature: '添加功能',
|
||||
automatic: '自动编排',
|
||||
stopResponding: '停止响应',
|
||||
|
@ -96,6 +96,7 @@ const translation = {
|
||||
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.',
|
||||
previewTitle: 'Preview',
|
||||
previewTitleButton: 'Preview',
|
||||
previewButton: 'Switching to Q&A format',
|
||||
previewSwitchTipStart: 'The current segment preview is in text format, switching to a question-and-answer format preview will',
|
||||
previewSwitchTipEnd: ' consume additional tokens',
|
||||
|
@ -96,6 +96,7 @@ const translation = {
|
||||
sideTipP3: '清洗则是对文本进行预处理,删除不必要的字符、符号或格式,使数据集更加干净、整洁,便于模型解析。',
|
||||
sideTipP4: '通过对数据集进行适当的分段和清洗,可以提高模型在实际应用中的表现,从而为用户提供更准确、更有价值的结果。',
|
||||
previewTitle: '分段预览',
|
||||
previewTitleButton: '预览',
|
||||
previewButton: '切换至 Q&A 形式',
|
||||
previewSwitchTipStart: '当前分段预览是文本模式,切换到 Q&A 模式将会',
|
||||
previewSwitchTipEnd: '消耗额外的 token',
|
||||
|
Loading…
Reference in New Issue
Block a user