feat: support assistant frontend (#2139)
Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
This commit is contained in:
parent
e65a2a400d
commit
7bbe12b2bd
194 changed files with 8726 additions and 1586 deletions
|
|
@ -38,15 +38,24 @@ 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 }] : []),
|
||||
{ name: t('common.appMenus.overview'), href: `/app/${appId}/overview`, icon: ChartBarSquareIcon, selectedIcon: ChartBarSquareSolidIcon },
|
||||
{ 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 },
|
||||
]
|
||||
return navs
|
||||
}, [appId, isCurrentWorkspaceManager, t])
|
||||
|
||||
const appModeName = response?.mode?.toUpperCase() === 'COMPLETION' ? t('common.appModes.completionApp') : t('common.appModes.chatApp')
|
||||
const appModeName = (() => {
|
||||
if (response?.mode?.toUpperCase() === 'COMPLETION')
|
||||
return t('app.newApp.completeApp')
|
||||
|
||||
const isAgent = !!response?.is_agent
|
||||
if (isAgent)
|
||||
return t('appDebug.assistantType.agentAssistant.name')
|
||||
|
||||
return t('appDebug.assistantType.chatAssistant.name')
|
||||
})()
|
||||
useEffect(() => {
|
||||
if (response?.name)
|
||||
document.title = `${(response.name || 'App')} - Dify`
|
||||
|
|
|
|||
|
|
@ -55,9 +55,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
|||
catch (e: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
message: `${t('app.appDeleteFailed')}${
|
||||
'message' in e ? `: ${e.message}` : ''
|
||||
}`,
|
||||
message: `${t('app.appDeleteFailed')}${'message' in e ? `: ${e.message}` : ''}`,
|
||||
})
|
||||
}
|
||||
setShowConfirmDelete(false)
|
||||
|
|
@ -141,7 +139,8 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
|||
if (showSettingsModal)
|
||||
return
|
||||
e.preventDefault()
|
||||
push(`/app/${app.id}/overview`)
|
||||
|
||||
push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`)
|
||||
}}
|
||||
className={style.listItem}
|
||||
>
|
||||
|
|
@ -173,7 +172,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
|||
{app.model_config?.pre_prompt}
|
||||
</div>
|
||||
<div className={style.listItemFooter}>
|
||||
<AppModeLabel mode={app.mode} />
|
||||
<AppModeLabel mode={app.mode} isAgent={app.is_agent} />
|
||||
</div>
|
||||
|
||||
{showConfirmDelete && (
|
||||
|
|
|
|||
|
|
@ -1,25 +1,53 @@
|
|||
'use client'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import style from '../list.module.css'
|
||||
import { type AppMode } from '@/types/app'
|
||||
import {
|
||||
AiText,
|
||||
CuteRobote,
|
||||
} from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import { BubbleText } from '@/app/components/base/icons/src/vender/solid/education'
|
||||
|
||||
export type AppModeLabelProps = {
|
||||
mode: AppMode
|
||||
isAgent?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AppModeLabel = ({
|
||||
mode,
|
||||
isAgent,
|
||||
className,
|
||||
}: AppModeLabelProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<span className={classNames('flex items-center w-fit h-6 gap-1 px-2 text-gray-500 text-xs border border-gray-100 rounded', className)}>
|
||||
<span className={classNames(style.listItemFooterIcon, mode === 'chat' && style.solidChatIcon, mode === 'completion' && style.solidCompletionIcon)} />
|
||||
{t(`app.modes.${mode}`)}
|
||||
</span>
|
||||
<div className={`inline-flex items-center px-2 h-6 rounded-md border border-gray-100 text-xs text-gray-500 ${className}`}>
|
||||
{
|
||||
mode === 'completion' && (
|
||||
<>
|
||||
<AiText className='mr-1 w-3 h-3 text-gray-400' />
|
||||
{t('app.newApp.completeApp')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
mode === 'chat' && !isAgent && (
|
||||
<>
|
||||
<BubbleText className='mr-1 w-3 h-3 text-gray-400' />
|
||||
{t('appDebug.assistantType.chatAssistant.name')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
mode === 'chat' && isAgent && (
|
||||
<>
|
||||
<CuteRobote className='mr-1 w-3 h-3 text-gray-400' />
|
||||
{t('appDebug.assistantType.agentAssistant.name')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import AppCard from './AppCard'
|
||||
import NewAppCard from './NewAppCard'
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
|
|
@ -10,17 +11,46 @@ import { fetchAppList } from '@/service/apps'
|
|||
import { useAppContext } from '@/context/app-context'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { CheckModal } from '@/hooks/use-pay'
|
||||
const getKey = (pageIndex: number, previousPageData: AppListResponse) => {
|
||||
if (!pageIndex || previousPageData.has_more)
|
||||
return { url: 'apps', params: { page: pageIndex + 1, limit: 30 } }
|
||||
import TabSlider from '@/app/components/base/tab-slider'
|
||||
import { SearchLg } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
|
||||
const getKey = (
|
||||
pageIndex: number,
|
||||
previousPageData: AppListResponse,
|
||||
activeTab: string,
|
||||
keywords: string,
|
||||
) => {
|
||||
if (!pageIndex || previousPageData.has_more) {
|
||||
const params: any = { url: 'apps', params: { page: pageIndex + 1, limit: 30, name: keywords } }
|
||||
|
||||
if (activeTab !== 'all')
|
||||
params.params.mode = activeTab
|
||||
|
||||
return params
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const Apps = () => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const { data, isLoading, setSize, mutate } = useSWRInfinite(getKey, fetchAppList, { revalidateFirstPage: false })
|
||||
const [activeTab, setActiveTab] = useState('all')
|
||||
const [keywords, setKeywords] = useState('')
|
||||
const [searchKeywords, setSearchKeywords] = useState('')
|
||||
|
||||
const { data, isLoading, setSize, mutate } = useSWRInfinite(
|
||||
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, searchKeywords),
|
||||
fetchAppList,
|
||||
{ revalidateFirstPage: false },
|
||||
)
|
||||
|
||||
const anchorRef = useRef<HTMLDivElement>(null)
|
||||
const options = [
|
||||
{ value: 'all', text: t('app.types.all') },
|
||||
{ value: 'chat', text: t('app.types.assistant') },
|
||||
{ value: 'completion', text: t('app.types.completion') },
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${t('app.title')} - Dify`
|
||||
|
|
@ -34,24 +64,72 @@ const Apps = () => {
|
|||
let observer: IntersectionObserver | undefined
|
||||
if (anchorRef.current) {
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting)
|
||||
setSize(size => size + 1)
|
||||
if (entries[0].isIntersecting && !isLoading)
|
||||
setSize((size: number) => size + 1)
|
||||
}, { rootMargin: '100px' })
|
||||
observer.observe(anchorRef.current)
|
||||
}
|
||||
return () => observer?.disconnect()
|
||||
}, [isLoading, setSize, anchorRef, mutate])
|
||||
|
||||
const { run: handleSearch } = useDebounceFn(() => {
|
||||
setSearchKeywords(keywords)
|
||||
}, { wait: 500 })
|
||||
|
||||
const handleKeywordsChange = (value: string) => {
|
||||
setKeywords(value)
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
handleKeywordsChange('')
|
||||
}
|
||||
|
||||
return (
|
||||
<><nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
|
||||
{ isCurrentWorkspaceManager
|
||||
&& <NewAppCard onSuccess={mutate} />}
|
||||
{data?.map(({ data: apps }) => apps.map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={mutate} />
|
||||
)))}
|
||||
<CheckModal />
|
||||
</nav>
|
||||
<div ref={anchorRef} className='h-0'> </div>
|
||||
<>
|
||||
<div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-gray-100 z-10 flex-wrap gap-y-2'>
|
||||
<div className="flex items-center px-2 w-[200px] h-8 rounded-lg bg-gray-200">
|
||||
<div className="pointer-events-none shrink-0 flex items-center mr-1.5 justify-center w-4 h-4">
|
||||
<SearchLg className="h-3.5 w-3.5 text-gray-500" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="query"
|
||||
className="grow block h-[18px] bg-gray-200 rounded-md border-0 text-gray-600 text-[13px] placeholder:text-gray-500 appearance-none outline-none"
|
||||
placeholder={t('common.operation.search')!}
|
||||
value={keywords}
|
||||
onChange={(e) => {
|
||||
handleKeywordsChange(e.target.value)
|
||||
}}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{
|
||||
keywords && (
|
||||
<div
|
||||
className='shrink-0 flex items-center justify-center w-4 h-4 cursor-pointer'
|
||||
onClick={handleClear}
|
||||
>
|
||||
<XCircle className='w-3.5 h-3.5 text-gray-400' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<TabSlider
|
||||
value={activeTab}
|
||||
onChange={setActiveTab}
|
||||
options={options}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
|
||||
{isCurrentWorkspaceManager
|
||||
&& <NewAppCard onSuccess={mutate} />}
|
||||
{data?.map(({ data: apps }: any) => apps.map((app: any) => (
|
||||
<AppCard key={app.id} app={app} onRefresh={mutate} />
|
||||
)))}
|
||||
<CheckModal />
|
||||
</nav>
|
||||
<div ref={anchorRef} className='h-0'> </div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@ import type { AppMode } from '@/types/app'
|
|||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { createApp, fetchAppTemplates } from '@/service/apps'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import AppsContext from '@/context/app-context'
|
||||
import AppsContext, { useAppContext } from '@/context/app-context'
|
||||
import EmojiPicker from '@/app/components/base/emoji-picker'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
import { AiText } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
|
||||
type NewAppDialogProps = {
|
||||
show: boolean
|
||||
|
|
@ -29,6 +30,8 @@ type NewAppDialogProps = {
|
|||
const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
||||
const router = useRouter()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const nameInputRef = useRef<HTMLInputElement>(null)
|
||||
|
|
@ -90,7 +93,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
|||
onClose()
|
||||
notify({ type: 'success', message: t('app.newApp.appCreated') })
|
||||
mutateApps()
|
||||
router.push(`/app/${app.id}/overview`)
|
||||
router.push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`)
|
||||
}
|
||||
catch (e) {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
|
|
@ -119,13 +122,6 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
|||
</>
|
||||
}
|
||||
>
|
||||
<h3 className={style.newItemCaption}>{t('app.newApp.captionName')}</h3>
|
||||
|
||||
<div className='flex items-center justify-between gap-3 mb-8'>
|
||||
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} />
|
||||
<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='overflow-y-auto'>
|
||||
<div className={style.newItemCaption}>
|
||||
<h3 className='inline'>{t('app.newApp.captionAppType')}</h3>
|
||||
|
|
@ -141,29 +137,9 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
{isWithTemplate
|
||||
? (
|
||||
<ul className='grid grid-cols-1 md:grid-cols-2 gap-4'>
|
||||
{templates?.data?.map((template, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={classNames(style.listItem, style.selectable, selectedTemplateIndex === index && style.selected)}
|
||||
onClick={() => setSelectedTemplateIndex(index)}
|
||||
>
|
||||
<div className={style.listItemTitle}>
|
||||
<AppIcon size='small' />
|
||||
<div className={style.listItemHeading}>
|
||||
<div className={style.listItemHeadingContent}>{template.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.listItemDescription}>{template.model_config?.pre_prompt}</div>
|
||||
<AppModeLabel mode={template.mode} className='mt-2' />
|
||||
{/* <AppModeLabel mode='chat' className='mt-2' /> */}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
: (
|
||||
|
||||
{!isWithTemplate && (
|
||||
(
|
||||
<>
|
||||
<ul className='grid grid-cols-1 md:grid-cols-2 gap-4'>
|
||||
<li
|
||||
|
|
@ -178,10 +154,10 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
|||
<div className={style.listItemHeadingContent}>{t('app.newApp.chatApp')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.listItemDescription}>{t('app.newApp.chatAppIntro')}</div>
|
||||
<div className={classNames(style.listItemFooter, 'justify-end')}>
|
||||
<div className={`${style.listItemDescription} ${style.noClip}`}>{t('app.newApp.chatAppIntro')}</div>
|
||||
{/* <div className={classNames(style.listItemFooter, 'justify-end')}>
|
||||
<a className={style.listItemLink} href='https://udify.app/chat/7CQBa5yyvYLSkZtx' target='_blank'>{t('app.newApp.previewDemo')}<span className={classNames(style.linkIcon, style.grayLinkIcon)} /></a>
|
||||
</div>
|
||||
</div> */}
|
||||
</li>
|
||||
<li
|
||||
className={classNames(style.listItem, style.selectable, newAppMode === 'completion' && style.selected)}
|
||||
|
|
@ -189,28 +165,65 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
|||
>
|
||||
<div className={style.listItemTitle}>
|
||||
<span className={style.newItemIcon}>
|
||||
<span className={classNames(style.newItemIconImage, style.newItemIconComplete)} />
|
||||
{/* <span className={classNames(style.newItemIconImage, style.newItemIconComplete)} /> */}
|
||||
<AiText className={classNames('w-5 h-5', newAppMode === 'completion' ? 'text-[#155EEF]' : 'text-gray-700')} />
|
||||
</span>
|
||||
<div className={style.listItemHeading}>
|
||||
<div className={style.listItemHeadingContent}>{t('app.newApp.completeApp')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.listItemDescription}>{t('app.newApp.completeAppIntro')}</div>
|
||||
<div className={classNames(style.listItemFooter, 'justify-end')}>
|
||||
<a className={style.listItemLink} href='https://udify.app/completion/aeFTj0VCb3Ok3TUE' target='_blank'>{t('app.newApp.previewDemo')}<span className={classNames(style.linkIcon, style.grayLinkIcon)} /></a>
|
||||
</div>
|
||||
<div className={`${style.listItemDescription} ${style.noClip}`}>{t('app.newApp.completeAppIntro')}</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div className='flex items-center h-[34px] mt-2'>
|
||||
<span
|
||||
className='inline-flex items-center gap-1 text-xs font-medium cursor-pointer text-primary-600'
|
||||
onClick={() => setIsWithTemplate(true)}
|
||||
>
|
||||
{t('app.newApp.showTemplates')}<span className={style.rightIcon} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
)
|
||||
)}
|
||||
|
||||
{isWithTemplate && (
|
||||
<ul className='grid grid-cols-1 md:grid-cols-2 gap-4'>
|
||||
{templates?.data?.map((template, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={classNames(style.listItem, style.selectable, selectedTemplateIndex === index && style.selected)}
|
||||
onClick={() => setSelectedTemplateIndex(index)}
|
||||
>
|
||||
<div className={style.listItemTitle}>
|
||||
<AppIcon size='small' />
|
||||
<div className={style.listItemHeading}>
|
||||
<div className={style.listItemHeadingContent}>{template.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.listItemDescription}>{template.model_config?.pre_prompt}</div>
|
||||
<div className='inline-block pl-3.5'>
|
||||
<AppModeLabel mode={template.mode} isAgent={template.model_config.agent_mode.enabled} className='mt-2' />
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<div className='mt-8'>
|
||||
<h3 className={style.newItemCaption}>{t('app.newApp.captionName')}</h3>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} />
|
||||
<input ref={nameInputRef} className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' placeholder={t('app.appNamePlaceholder') || ''} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
!isWithTemplate && (
|
||||
<div className='flex items-center h-[34px] mt-2'>
|
||||
<span
|
||||
className='inline-flex items-center gap-1 text-xs font-medium cursor-pointer text-primary-600'
|
||||
onClick={() => setIsWithTemplate(true)}
|
||||
>
|
||||
{t('app.newApp.showTemplates')}<span className={style.rightIcon} />
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
{isAppsFull && <AppsFull loc='app-create' />}
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const AppList = async () => {
|
|||
const { t } = await translate(locale, 'app')
|
||||
|
||||
return (
|
||||
<div className='flex flex-col overflow-auto bg-gray-100 shrink-0 grow'>
|
||||
<div className='relative flex flex-col overflow-y-auto bg-gray-100 shrink-0 h-0 grow'>
|
||||
<Apps />
|
||||
<footer className='px-12 py-6 grow-0 shrink-0'>
|
||||
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('join')}</h3>
|
||||
|
|
|
|||
|
|
@ -10,19 +10,20 @@
|
|||
.listItem.selectable {
|
||||
@apply relative bg-gray-50 outline outline-1 outline-gray-200 -outline-offset-1 shadow-none hover:bg-none hover:shadow-none hover:outline-primary-200 transition-colors;
|
||||
}
|
||||
|
||||
.listItem.selectable * {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.listItem.selectable::before {
|
||||
content: "";
|
||||
@apply absolute top-0 left-0 block w-full h-full rounded-lg pointer-events-none opacity-0 transition-opacity duration-200 ease-in-out hover:opacity-100;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
background: linear-gradient(0deg,
|
||||
rgba(235, 245, 255, 0.5),
|
||||
rgba(235, 245, 255, 0.5)
|
||||
),
|
||||
rgba(235, 245, 255, 0.5)),
|
||||
#ffffff;
|
||||
}
|
||||
|
||||
.listItem.selectable:hover::before {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
|
@ -30,6 +31,7 @@
|
|||
.listItem.selected {
|
||||
@apply border-primary-600 hover:border-primary-600 border-2;
|
||||
}
|
||||
|
||||
.listItem.selected::before {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
|
@ -37,9 +39,11 @@
|
|||
.appIcon {
|
||||
@apply flex items-center justify-center w-8 h-8 bg-pink-100 rounded-lg grow-0 shrink-0;
|
||||
}
|
||||
|
||||
.appIcon.medium {
|
||||
@apply w-9 h-9;
|
||||
}
|
||||
|
||||
.appIcon.large {
|
||||
@apply w-10 h-10;
|
||||
}
|
||||
|
|
@ -47,34 +51,48 @@
|
|||
.newItemIcon {
|
||||
@apply flex items-center justify-center w-8 h-8 transition-colors duration-200 ease-in-out border border-gray-200 rounded-lg hover:bg-white grow-0 shrink-0;
|
||||
}
|
||||
|
||||
.listItem:hover .newItemIcon {
|
||||
@apply bg-gray-50 border-primary-100;
|
||||
}
|
||||
|
||||
.newItemCard .newItemIcon {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
|
||||
.newItemCard:hover .newItemIcon {
|
||||
@apply bg-white;
|
||||
}
|
||||
|
||||
.selectable .newItemIcon {
|
||||
@apply bg-gray-50;
|
||||
}
|
||||
|
||||
.selectable:hover .newItemIcon {
|
||||
@apply bg-primary-50;
|
||||
}
|
||||
|
||||
.newItemIconImage {
|
||||
@apply grow-0 shrink-0 block w-4 h-4 bg-center bg-contain transition-colors duration-200 ease-in-out;
|
||||
color: #1f2a37;
|
||||
}
|
||||
|
||||
.listItem:hover .newIconImage {
|
||||
@apply text-primary-600;
|
||||
}
|
||||
|
||||
.newItemIconAdd {
|
||||
background-image: url("./apps/assets/add.svg");
|
||||
}
|
||||
|
||||
.newItemIconChat {
|
||||
background-image: url("./apps/assets/chat.svg");
|
||||
background-image: url("~@/app/components/base/icons/assets/public/header-nav/studio/Robot.svg");
|
||||
}
|
||||
|
||||
.selected .newItemIconChat {
|
||||
background-image: url("~@/app/components/base/icons/assets/public/header-nav/studio/Robot-Active.svg");
|
||||
}
|
||||
|
||||
.newItemIconComplete {
|
||||
background-image: url("./apps/assets/completion.svg");
|
||||
}
|
||||
|
|
@ -94,14 +112,17 @@
|
|||
.actionIconWrapper {
|
||||
@apply hidden h-8 w-8 p-2 rounded-md border-none hover:bg-gray-100 !important;
|
||||
}
|
||||
|
||||
.listItem:hover .actionIconWrapper {
|
||||
@apply !inline-flex;
|
||||
}
|
||||
|
||||
.deleteDatasetIcon {
|
||||
@apply hidden grow-0 shrink-0 basis-8 w-8 h-8 rounded-lg transition-colors duration-200 ease-in-out bg-white border border-gray-200 hover:bg-gray-100 bg-center bg-no-repeat;
|
||||
background-size: 16px;
|
||||
background-image: url('~@/assets/delete.svg');
|
||||
}
|
||||
|
||||
.listItem:hover .deleteDatasetIcon {
|
||||
@apply block;
|
||||
}
|
||||
|
|
@ -110,9 +131,14 @@
|
|||
@apply mb-3 px-[14px] h-9 text-xs leading-normal text-gray-500 line-clamp-2;
|
||||
}
|
||||
|
||||
.listItemDescription.noClip {
|
||||
@apply line-clamp-none;
|
||||
}
|
||||
|
||||
.listItemFooter {
|
||||
@apply flex items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px];
|
||||
}
|
||||
|
||||
.listItemFooter.datasetCardFooter {
|
||||
@apply flex items-center gap-4 text-xs text-gray-500;
|
||||
}
|
||||
|
|
@ -124,18 +150,23 @@
|
|||
.listItemFooterIcon {
|
||||
@apply block w-3 h-3 bg-center bg-contain;
|
||||
}
|
||||
|
||||
.solidChatIcon {
|
||||
background-image: url("./apps/assets/chat-solid.svg");
|
||||
}
|
||||
|
||||
.solidCompletionIcon {
|
||||
background-image: url("./apps/assets/completion-solid.svg");
|
||||
}
|
||||
|
||||
.docIcon {
|
||||
background-image: url("./datasets/assets/doc.svg");
|
||||
}
|
||||
|
||||
.textIcon {
|
||||
background-image: url("./datasets/assets/text.svg");
|
||||
}
|
||||
|
||||
.applicationIcon {
|
||||
background-image: url("./datasets/assets/application.svg");
|
||||
}
|
||||
|
|
@ -143,6 +174,7 @@
|
|||
.newItemCardHeading {
|
||||
@apply transition-colors duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.listItem:hover .newItemCardHeading {
|
||||
@apply text-primary-600;
|
||||
}
|
||||
|
|
@ -150,6 +182,7 @@
|
|||
.listItemLink {
|
||||
@apply inline-flex items-center gap-1 text-xs text-gray-400 transition-colors duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.listItem:hover .listItemLink {
|
||||
@apply text-primary-600;
|
||||
}
|
||||
|
|
@ -162,6 +195,7 @@
|
|||
.linkIcon.grayLinkIcon {
|
||||
background-image: url("./apps/assets/link-gray.svg");
|
||||
}
|
||||
|
||||
.listItem:hover .grayLinkIcon {
|
||||
background-image: url("./apps/assets/link.svg");
|
||||
}
|
||||
|
|
@ -191,6 +225,7 @@
|
|||
.newItemCaption {
|
||||
@apply inline-flex items-center mb-2 text-sm font-medium;
|
||||
}
|
||||
|
||||
/* #endregion new app dialog */
|
||||
|
||||
.unavailable {
|
||||
|
|
@ -199,4 +234,4 @@
|
|||
|
||||
.listItem:hover .unavailable {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
10
web/app/(commonLayout)/tools/custom/page.tsx
Normal file
10
web/app/(commonLayout)/tools/custom/page.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
|
||||
const Custom = () => {
|
||||
return (
|
||||
<div>
|
||||
Custom
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Custom
|
||||
23
web/app/(commonLayout)/tools/page.tsx
Normal file
23
web/app/(commonLayout)/tools/page.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import React, { useEffect } from 'react'
|
||||
import Tools from '@/app/components/tools'
|
||||
import { LOC } from '@/app/components/tools/types'
|
||||
|
||||
const Layout: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${t('tools.title')} - Dify`
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='overflow-hidden' style={{
|
||||
height: 'calc(100vh - 56px)',
|
||||
}}>
|
||||
<Tools loc={LOC.tools} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Layout)
|
||||
10
web/app/(commonLayout)/tools/third-part/page.tsx
Normal file
10
web/app/(commonLayout)/tools/third-part/page.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
|
||||
const ThirdPart = () => {
|
||||
return (
|
||||
<div>
|
||||
Third part
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ThirdPart
|
||||
Loading…
Add table
Add a link
Reference in a new issue