Unverified Commit 37c3b897 authored by crazywoola's avatar crazywoola Committed by GitHub

Feature/add emoji (#103)

parent f68b05d5
...@@ -49,7 +49,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => { ...@@ -49,7 +49,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
return null return null
return ( return (
<div className={cn(s.app, 'flex', 'overflow-hidden')}> <div className={cn(s.app, 'flex', 'overflow-hidden')}>
<AppSideBar title={response.name} desc={appModeName} navigation={navigation} /> <AppSideBar title={response.name} icon={response.icon} icon_background={response.icon_background} desc={appModeName} navigation={navigation} />
<div className="bg-white grow">{children}</div> <div className="bg-white grow">{children}</div>
</div> </div>
) )
......
...@@ -47,7 +47,7 @@ const AppCard = ({ ...@@ -47,7 +47,7 @@ const AppCard = ({
<> <>
<Link href={`/app/${app.id}/overview`} className={style.listItem}> <Link href={`/app/${app.id}/overview`} className={style.listItem}>
<div className={style.listItemTitle}> <div className={style.listItemTitle}>
<AppIcon size='small' /> <AppIcon size='small' icon={app.icon} background={app.icon_background}/>
<div className={style.listItemHeading}> <div className={style.listItemHeading}>
<div className={style.listItemHeadingContent}>{app.name}</div> <div className={style.listItemHeadingContent}>{app.name}</div>
</div> </div>
......
...@@ -17,6 +17,7 @@ const Apps = () => { ...@@ -17,6 +17,7 @@ const Apps = () => {
{apps.map(app => (<AppCard key={app.id} app={app} />))} {apps.map(app => (<AppCard key={app.id} app={app} />))}
<NewAppCard /> <NewAppCard />
</nav> </nav>
) )
} }
......
...@@ -9,7 +9,6 @@ import NewAppDialog from './NewAppDialog' ...@@ -9,7 +9,6 @@ import NewAppDialog from './NewAppDialog'
const CreateAppCard = () => { const CreateAppCard = () => {
const { t } = useTranslation() const { t } = useTranslation()
const [showNewAppDialog, setShowNewAppDialog] = useState(false) const [showNewAppDialog, setShowNewAppDialog] = useState(false)
return ( return (
<a className={classNames(style.listItem, style.newItemCard)} onClick={() => setShowNewAppDialog(true)}> <a className={classNames(style.listItem, style.newItemCard)} onClick={() => setShowNewAppDialog(true)}>
<div className={style.listItemTitle}> <div className={style.listItemTitle}>
......
...@@ -17,6 +17,8 @@ import { createApp, fetchAppTemplates } from '@/service/apps' ...@@ -17,6 +17,8 @@ import { createApp, fetchAppTemplates } from '@/service/apps'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import AppsContext from '@/context/app-context' import AppsContext from '@/context/app-context'
import EmojiPicker from '@/app/components/base/emoji-picker'
type NewAppDialogProps = { type NewAppDialogProps = {
show: boolean show: boolean
onClose?: () => void onClose?: () => void
...@@ -31,6 +33,11 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => { ...@@ -31,6 +33,11 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => {
const [newAppMode, setNewAppMode] = useState<AppMode>() const [newAppMode, setNewAppMode] = useState<AppMode>()
const [isWithTemplate, setIsWithTemplate] = useState(false) const [isWithTemplate, setIsWithTemplate] = useState(false)
const [selectedTemplateIndex, setSelectedTemplateIndex] = useState<number>(-1) const [selectedTemplateIndex, setSelectedTemplateIndex] = useState<number>(-1)
// Emoji Picker
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
const [emoji, setEmoji] = useState({ icon: '🍌', icon_background: '#FFEAD5' })
const mutateApps = useContextSelector(AppsContext, state => state.mutateApps) const mutateApps = useContextSelector(AppsContext, state => state.mutateApps)
const { data: templates, mutate } = useSWR({ url: '/app-templates' }, fetchAppTemplates) const { data: templates, mutate } = useSWR({ url: '/app-templates' }, fetchAppTemplates)
...@@ -67,6 +74,8 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => { ...@@ -67,6 +74,8 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => {
try { try {
const app = await createApp({ const app = await createApp({
name, name,
icon: emoji.icon,
icon_background: emoji.icon_background,
mode: isWithTemplate ? templates.data[selectedTemplateIndex].mode : newAppMode!, mode: isWithTemplate ? templates.data[selectedTemplateIndex].mode : newAppMode!,
config: isWithTemplate ? templates.data[selectedTemplateIndex].model_config : undefined, config: isWithTemplate ? templates.data[selectedTemplateIndex].model_config : undefined,
}) })
...@@ -80,9 +89,20 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => { ...@@ -80,9 +89,20 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => {
notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
} }
isCreatingRef.current = false isCreatingRef.current = false
}, [isWithTemplate, newAppMode, notify, router, templates, selectedTemplateIndex]) }, [isWithTemplate, newAppMode, notify, router, templates, selectedTemplateIndex, emoji])
return ( return <>
{showEmojiPicker && <EmojiPicker
onSelect={(icon, icon_background) => {
console.log(icon, icon_background)
setEmoji({ icon, icon_background })
setShowEmojiPicker(false)
}}
onClose={() => {
setEmoji({ icon: '🍌', icon_background: '#FFEAD5' })
setShowEmojiPicker(false)
}}
/>}
<Dialog <Dialog
show={show} show={show}
title={t('app.newApp.startToCreate')} title={t('app.newApp.startToCreate')}
...@@ -96,7 +116,7 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => { ...@@ -96,7 +116,7 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => {
<h3 className={style.newItemCaption}>{t('app.newApp.captionName')}</h3> <h3 className={style.newItemCaption}>{t('app.newApp.captionName')}</h3>
<div className='flex items-center justify-between gap-3 mb-8'> <div className='flex items-center justify-between gap-3 mb-8'>
<AppIcon size='large' /> <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' /> <input ref={nameInputRef} className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' />
</div> </div>
...@@ -187,7 +207,7 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => { ...@@ -187,7 +207,7 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => {
)} )}
</div> </div>
</Dialog> </Dialog>
) </>
} }
export default NewAppDialog export default NewAppDialog
...@@ -155,6 +155,8 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => { ...@@ -155,6 +155,8 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
<div className='flex' style={{ height: 'calc(100vh - 56px)' }}> <div className='flex' style={{ height: 'calc(100vh - 56px)' }}>
{!hideSideBar && <AppSideBar {!hideSideBar && <AppSideBar
title={datasetRes?.name || '--'} title={datasetRes?.name || '--'}
icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
icon_background={datasetRes?.icon_background || '#F5F5F5'}
desc={datasetRes?.description || '--'} desc={datasetRes?.description || '--'}
navigation={navigation} navigation={navigation}
extraInfo={<ExtraInfo />} extraInfo={<ExtraInfo />}
......
...@@ -15,7 +15,8 @@ export function randomString(length: number) { ...@@ -15,7 +15,8 @@ export function randomString(length: number) {
export type IAppBasicProps = { export type IAppBasicProps = {
iconType?: 'app' | 'api' | 'dataset' iconType?: 'app' | 'api' | 'dataset'
iconUrl?: string icon?: string,
icon_background?: string,
name: string name: string
type: string | React.ReactNode type: string | React.ReactNode
hoverTip?: string hoverTip?: string
...@@ -41,15 +42,20 @@ const ICON_MAP = { ...@@ -41,15 +42,20 @@ const ICON_MAP = {
'dataset': <AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' /> 'dataset': <AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' />
} }
export default function AppBasic({ iconUrl, name, type, hoverTip, textStyle, iconType = 'app' }: IAppBasicProps) { export default function AppBasic({ icon, icon_background, name, type, hoverTip, textStyle, iconType = 'app' }: IAppBasicProps) {
return ( return (
<div className="flex items-start"> <div className="flex items-start">
{iconUrl && ( {icon && icon_background && iconType === 'app' && (
<div className='flex-shrink-0 mr-3'> <div className='flex-shrink-0 mr-3'>
{/* <img className="inline-block rounded-lg h-9 w-9" src={iconUrl} alt={name} /> */} <AppIcon icon={icon} background={icon_background} />
{ICON_MAP[iconType]}
</div> </div>
)} )}
{iconType !== 'app' &&
<div className='flex-shrink-0 mr-3'>
{ICON_MAP[iconType]}
</div>
}
<div className="group"> <div className="group">
<div className={`flex flex-row items-center text-sm font-semibold text-gray-700 group-hover:text-gray-900 ${textStyle?.main}`}> <div className={`flex flex-row items-center text-sm font-semibold text-gray-700 group-hover:text-gray-900 ${textStyle?.main}`}>
{name} {name}
......
...@@ -7,6 +7,8 @@ export type IAppDetailNavProps = { ...@@ -7,6 +7,8 @@ export type IAppDetailNavProps = {
iconType?: 'app' | 'dataset' iconType?: 'app' | 'dataset'
title: string title: string
desc: string desc: string
icon: string
icon_background: string
navigation: Array<{ navigation: Array<{
name: string name: string
href: string href: string
...@@ -16,13 +18,12 @@ export type IAppDetailNavProps = { ...@@ -16,13 +18,12 @@ export type IAppDetailNavProps = {
extraInfo?: React.ReactNode extraInfo?: React.ReactNode
} }
const sampleAppIconUrl = 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80'
const AppDetailNav: FC<IAppDetailNavProps> = ({ title, desc, navigation, extraInfo, iconType = 'app' }) => { const AppDetailNav: FC<IAppDetailNavProps> = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }) => {
return ( return (
<div className="flex flex-col w-56 overflow-y-auto bg-white border-r border-gray-200 shrink-0"> <div className="flex flex-col w-56 overflow-y-auto bg-white border-r border-gray-200 shrink-0">
<div className="flex flex-shrink-0 p-4"> <div className="flex flex-shrink-0 p-4">
<AppBasic iconType={iconType} iconUrl={sampleAppIconUrl} name={title} type={desc} /> <AppBasic iconType={iconType} icon={icon} icon_background={icon_background} name={title} type={desc} />
</div> </div>
<nav className="flex-1 p-4 space-y-1 bg-white"> <nav className="flex-1 p-4 space-y-1 bg-white">
{navigation.map((item, index) => { {navigation.map((item, index) => {
......
...@@ -29,9 +29,6 @@ export type IAppCardProps = { ...@@ -29,9 +29,6 @@ export type IAppCardProps = {
onGenerateCode?: () => Promise<any> onGenerateCode?: () => Promise<any>
} }
// todo: get image url from appInfo
const defaultUrl = 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80'
function AppCard({ function AppCard({
appInfo, appInfo,
cardType = 'app', cardType = 'app',
...@@ -104,7 +101,8 @@ function AppCard({ ...@@ -104,7 +101,8 @@ function AppCard({
<div className="mb-2.5 flex flex-row items-start justify-between"> <div className="mb-2.5 flex flex-row items-start justify-between">
<AppBasic <AppBasic
iconType={isApp ? 'app' : 'api'} iconType={isApp ? 'app' : 'api'}
iconUrl={defaultUrl} icon={appInfo.icon}
icon_background={appInfo.icon_background}
name={basicName} name={basicName}
type={ type={
isApp isApp
......
...@@ -2,6 +2,11 @@ import type { FC } from 'react' ...@@ -2,6 +2,11 @@ import type { FC } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import style from './style.module.css' import style from './style.module.css'
import data from '@emoji-mart/data'
import { init } from 'emoji-mart'
init({ data })
export type AppIconProps = { export type AppIconProps = {
size?: 'tiny' | 'small' | 'medium' | 'large' size?: 'tiny' | 'small' | 'medium' | 'large'
rounded?: boolean rounded?: boolean
...@@ -9,14 +14,17 @@ export type AppIconProps = { ...@@ -9,14 +14,17 @@ export type AppIconProps = {
background?: string background?: string
className?: string className?: string
innerIcon?: React.ReactNode innerIcon?: React.ReactNode
onClick?: () => void
} }
const AppIcon: FC<AppIconProps> = ({ const AppIcon: FC<AppIconProps> = ({
size = 'medium', size = 'medium',
rounded = false, rounded = false,
icon,
background, background,
className, className,
innerIcon, innerIcon,
onClick,
}) => { }) => {
return ( return (
<span <span
...@@ -29,8 +37,9 @@ const AppIcon: FC<AppIconProps> = ({ ...@@ -29,8 +37,9 @@ const AppIcon: FC<AppIconProps> = ({
style={{ style={{
background, background,
}} }}
onClick={onClick}
> >
{innerIcon ? innerIcon : <>🤖</>} {innerIcon ? innerIcon : icon && icon !== '' ? <em-emoji id={icon} /> : <em-emoji id={'banana'} />}
</span> </span>
) )
} }
......
'use client'
import data from '@emoji-mart/data'
import { init, SearchIndex } from 'emoji-mart'
// import AppIcon from '@/app/components/base/app-icon'
import cn from 'classnames'
import Divider from '@/app/components/base/divider'
import Button from '@/app/components/base/button'
import s from './style.module.css'
import { useState, FC, ChangeEvent } from 'react'
import {
MagnifyingGlassIcon
} from '@heroicons/react/24/outline'
import React from 'react'
import Modal from '@/app/components/base/modal'
declare global {
namespace JSX {
interface IntrinsicElements {
'em-emoji': React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement>,
HTMLElement
>;
}
}
}
init({ data })
async function search(value: string) {
const emojis = await SearchIndex.search(value) || []
const results = emojis.map((emoji: any) => {
return emoji.skins[0].native
})
return results
}
const backgroundColors = [
'#FFEAD5',
'#E4FBCC',
'#D3F8DF',
'#E0F2FE',
'#E0EAFF',
'#EFF1F5',
'#FBE8FF',
'#FCE7F6',
'#FEF7C3',
'#E6F4D7',
'#D5F5F6',
'#D1E9FF',
'#D1E0FF',
'#D5D9EB',
'#ECE9FE',
'#FFE4E8',
]
interface IEmojiPickerProps {
isModal?: boolean
onSelect?: (emoji: string, background: string) => void
onClose?: () => void
}
const EmojiPicker: FC<IEmojiPickerProps> = ({
isModal = true,
onSelect,
onClose
}) => {
const { categories } = data as any
const [selectedEmoji, setSelectedEmoji] = useState('')
const [selectedBackground, setSelectedBackground] = useState(backgroundColors[0])
const [searchedEmojis, setSearchedEmojis] = useState([])
const [isSearching, setIsSearching] = useState(false)
return isModal ? <Modal
onClose={() => { }}
isShow
closable={false}
className={cn(s.container, '!w-[362px] !p-0')}
>
<div className='flex flex-col items-center w-full p-3'>
<div className="relative w-full">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<MagnifyingGlassIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
</div>
<input
type="search"
id="search"
className='block w-full h-10 px-3 pl-10 text-sm font-normal bg-gray-100 rounded-lg'
placeholder="Search emojis..."
onChange={async (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.value === '') {
setIsSearching(false)
return
} else {
setIsSearching(true)
const emojis = await search(e.target.value)
setSearchedEmojis(emojis)
}
}}
/>
</div>
</div>
<Divider className='m-0 mb-3' />
<div className="w-full max-h-[200px] overflow-x-hidden overflow-y-auto px-3">
{isSearching && <>
<div key={`category-search`} className='flex flex-col'>
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>Search</p>
<div className='w-full h-full grid grid-cols-8 gap-1'>
{searchedEmojis.map((emoji: string, index: number) => {
return <div
key={`emoji-search-${index}`}
className='inline-flex w-10 h-10 rounded-lg items-center justify-center'
onClick={() => {
setSelectedEmoji(emoji)
}}
>
<div className='cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-gray-300'>
<em-emoji id={emoji} />
</div>
</div>
})}
</div>
</div>
</>}
{categories.map((category: any, index: number) => {
return <div key={`category-${index}`} className='flex flex-col'>
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>{category.id}</p>
<div className='w-full h-full grid grid-cols-8 gap-1'>
{category.emojis.map((emoji: string, index: number) => {
return <div
key={`emoji-${index}`}
className='inline-flex w-10 h-10 rounded-lg items-center justify-center'
onClick={() => {
setSelectedEmoji(emoji)
}}
>
<div className='cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-gray-300'>
<em-emoji id={emoji} />
</div>
</div>
})}
</div>
</div>
})}
</div>
{/* Color Select */}
<div className={cn('flex flex-col p-3 ', selectedEmoji == '' ? 'opacity-25' : '')}>
<p className='font-medium uppercase text-xs text-[#101828] mb-2'>Choose Style</p>
<div className='w-full h-full grid grid-cols-8 gap-1'>
{backgroundColors.map((color) => {
return <div
key={color}
className={
cn(
'cursor-pointer',
`hover:ring-1 ring-offset-1`,
'inline-flex w-10 h-10 rounded-lg items-center justify-center',
color === selectedBackground ? `ring-1 ring-gray-300` : '',
)}
onClick={() => {
setSelectedBackground(color)
}}
>
<div className={cn(
'w-8 h-8 p-1 flex items-center justify-center rounded-lg',
)
} style={{ background: color }}>
{selectedEmoji !== '' && <em-emoji id={selectedEmoji} />}
</div>
</div>
})}
</div>
</div>
<Divider className='m-0' />
<div className='w-full flex items-center justify-center p-3 gap-2'>
<Button type="default" className='w-full' onClick={() => {
onClose && onClose()
}}>
Cancel
</Button>
<Button
disabled={selectedEmoji == ''}
type="primary"
className='w-full'
onClick={() => {
onSelect && onSelect(selectedEmoji, selectedBackground)
}}>
OK
</Button>
</div>
</Modal> : <>
</>
}
export default EmojiPicker
.container {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 362px;
max-height: 552px;
border: 0.5px solid #EAECF0;
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
border-radius: 12px;
background: #fff;
}
...@@ -25,51 +25,51 @@ export default function Modal({ ...@@ -25,51 +25,51 @@ export default function Modal({
closable = false, closable = false,
}: IModal) { }: IModal) {
return ( return (
<Transition appear show={isShow} as={Fragment}> <Transition appear show={isShow} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onClose}> <Dialog as="div" className="relative z-10" onClose={onClose}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<div className="fixed inset-0 bg-black bg-opacity-25" /> <div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 overflow-y-auto"> <div className="fixed inset-0 overflow-y-auto">
<div className={`flex min-h-full items-center justify-center p-4 text-center ${wrapperClassName}`}> <div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0 scale-95" enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100" enterTo="opacity-100 scale-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100" leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95" leaveTo="opacity-0 scale-95"
> >
<Dialog.Panel className={`w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all ${className}`}> <Dialog.Panel className={`w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all ${className}`}>
{title && <Dialog.Title {title && <Dialog.Title
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-900" className="text-lg font-medium leading-6 text-gray-900"
> >
{title} {title}
</Dialog.Title>} </Dialog.Title>}
{description && <Dialog.Description className='text-gray-500 text-xs font-normal mt-2'> {description && <Dialog.Description className='text-gray-500 text-xs font-normal mt-2'>
{description} {description}
</Dialog.Description>} </Dialog.Description>}
{closable {closable
&& <div className='absolute top-6 right-6 w-5 h-5 rounded-2xl flex items-center justify-center hover:cursor-pointer hover:bg-gray-100'> && <div className='absolute top-6 right-6 w-5 h-5 rounded-2xl flex items-center justify-center hover:cursor-pointer hover:bg-gray-100'>
<XMarkIcon className='w-4 h-4 text-gray-500' onClick={onClose} /> <XMarkIcon className='w-4 h-4 text-gray-500' onClick={onClose} />
</div>} </div>}
{children} {children}
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>
</div> </div>
</div> </div>
</Dialog> </Dialog>
</Transition> </Transition>
) )
} }
...@@ -69,11 +69,13 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan ...@@ -69,11 +69,13 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
text={t('common.menus.apps')} text={t('common.menus.apps')}
activeSegment={['apps', 'app']} activeSegment={['apps', 'app']}
link='/apps' link='/apps'
curNav={curApp && { id: curApp.id, name: curApp.name }} curNav={curApp && { id: curApp.id, name: curApp.name ,icon: curApp.icon, icon_background: curApp.icon_background}}
navs={appItems.map(item => ({ navs={appItems.map(item => ({
id: item.id, id: item.id,
name: item.name, name: item.name,
link: `/app/${item.id}/overview` link: `/app/${item.id}/overview`,
icon: item.icon,
icon_background: item.icon_background
}))} }))}
createText={t('common.menus.newApp')} createText={t('common.menus.newApp')}
onCreate={() => setShowNewAppDialog(true)} onCreate={() => setShowNewAppDialog(true)}
...@@ -91,11 +93,13 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan ...@@ -91,11 +93,13 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
text={t('common.menus.datasets')} text={t('common.menus.datasets')}
activeSegment='datasets' activeSegment='datasets'
link='/datasets' link='/datasets'
curNav={currentDataset && { id: currentDataset.id, name: currentDataset.name }} curNav={currentDataset && { id: currentDataset.id, name: currentDataset.name, icon: currentDataset.icon, icon_background: currentDataset.icon_background }}
navs={datasets.map(dataset => ({ navs={datasets.map(dataset => ({
id: dataset.id, id: dataset.id,
name: dataset.name, name: dataset.name,
link: `/datasets/${dataset.id}/documents` link: `/datasets/${dataset.id}/documents`,
icon: dataset.icon,
icon_background: dataset.icon_background
}))} }))}
createText={t('common.menus.newDataset')} createText={t('common.menus.newDataset')}
onCreate={() => router.push('/datasets/create')} onCreate={() => router.push('/datasets/create')}
......
...@@ -10,6 +10,8 @@ type NavItem = { ...@@ -10,6 +10,8 @@ type NavItem = {
id: string id: string
name: string name: string
link: string link: string
icon: string
icon_background: string
} }
export interface INavSelectorProps { export interface INavSelectorProps {
navs: NavItem[] navs: NavItem[]
...@@ -66,7 +68,7 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps) ...@@ -66,7 +68,7 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps)
<Menu.Item key={nav.id}> <Menu.Item key={nav.id}>
<div className={itemClassName} onClick={() => router.push(nav.link)}> <div className={itemClassName} onClick={() => router.push(nav.link)}>
<div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'> <div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'>
<AppIcon size='tiny' /> <AppIcon size='tiny' icon={nav.icon} background={nav.icon_background}/>
<div className='flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded'> <div className='flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded'>
<Indicator /> <Indicator />
</div> </div>
...@@ -102,4 +104,4 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps) ...@@ -102,4 +104,4 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps)
) )
} }
export default NavSelector export default NavSelector
\ No newline at end of file
...@@ -441,6 +441,8 @@ const Main: FC<IMainProps> = () => { ...@@ -441,6 +441,8 @@ const Main: FC<IMainProps> = () => {
<div className='bg-gray-100'> <div className='bg-gray-100'>
<Header <Header
title={siteInfo.title} title={siteInfo.title}
icon={siteInfo.icon || ''}
icon_background={siteInfo.icon_background || '#FFEAD5'}
isMobile={isMobile} isMobile={isMobile}
onShowSideBar={showSidebar} onShowSideBar={showSidebar}
onCreateNewChat={() => handleConversationIdChange('-1')} onCreateNewChat={() => handleConversationIdChange('-1')}
......
...@@ -7,6 +7,8 @@ import { ...@@ -7,6 +7,8 @@ import {
} from '@heroicons/react/24/solid' } from '@heroicons/react/24/solid'
export type IHeaderProps = { export type IHeaderProps = {
title: string title: string
icon: string
icon_background: string
isMobile?: boolean isMobile?: boolean
onShowSideBar?: () => void onShowSideBar?: () => void
onCreateNewChat?: () => void onCreateNewChat?: () => void
...@@ -14,6 +16,8 @@ export type IHeaderProps = { ...@@ -14,6 +16,8 @@ export type IHeaderProps = {
const Header: FC<IHeaderProps> = ({ const Header: FC<IHeaderProps> = ({
title, title,
isMobile, isMobile,
icon,
icon_background,
onShowSideBar, onShowSideBar,
onCreateNewChat, onCreateNewChat,
}) => { }) => {
...@@ -28,7 +32,7 @@ const Header: FC<IHeaderProps> = ({ ...@@ -28,7 +32,7 @@ const Header: FC<IHeaderProps> = ({
</div> </div>
) : <div></div>} ) : <div></div>}
<div className='flex items-center space-x-2'> <div className='flex items-center space-x-2'>
<AppIcon size="small" /> <AppIcon size="small" icon={icon} background={icon_background} />
<div className=" text-sm text-gray-800 font-bold">{title}</div> <div className=" text-sm text-gray-800 font-bold">{title}</div>
</div> </div>
{isMobile ? ( {isMobile ? (
......
...@@ -3,6 +3,8 @@ import { AppMode } from './app' ...@@ -3,6 +3,8 @@ import { AppMode } from './app'
export type DataSet = { export type DataSet = {
id: string id: string
name: string name: string
icon: string
icon_background: string
description: string description: string
permission: 'only_me' | 'all_team_members' permission: 'only_me' | 'all_team_members'
data_source_type: 'upload_file' data_source_type: 'upload_file'
......
...@@ -11,6 +11,8 @@ export type ConversationItem = { ...@@ -11,6 +11,8 @@ export type ConversationItem = {
export type SiteInfo = { export type SiteInfo = {
title: string title: string
icon: string
icon_background: string
description: string description: string
default_language: Locale default_language: Locale
prompt_public: boolean prompt_public: boolean
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"fix": "next lint --fix" "fix": "next lint --fix"
}, },
"dependencies": { "dependencies": {
"@emoji-mart/data": "^1.1.2",
"@formatjs/intl-localematcher": "^0.2.32", "@formatjs/intl-localematcher": "^0.2.32",
"@headlessui/react": "^1.7.13", "@headlessui/react": "^1.7.13",
"@heroicons/react": "^2.0.16", "@heroicons/react": "^2.0.16",
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"echarts": "^5.4.1", "echarts": "^5.4.1",
"echarts-for-react": "^3.0.2", "echarts-for-react": "^3.0.2",
"emoji-mart": "^5.5.2",
"eslint": "8.36.0", "eslint": "8.36.0",
"eslint-config-next": "13.2.4", "eslint-config-next": "13.2.4",
"i18next": "^22.4.13", "i18next": "^22.4.13",
......
...@@ -16,8 +16,8 @@ export const fetchAppTemplates: Fetcher<AppTemplatesResponse, { url: string }> = ...@@ -16,8 +16,8 @@ export const fetchAppTemplates: Fetcher<AppTemplatesResponse, { url: string }> =
return get(url) as Promise<AppTemplatesResponse> return get(url) as Promise<AppTemplatesResponse>
} }
export const createApp: Fetcher<AppDetailResponse, { name: string; mode: AppMode; config?: ModelConfig }> = ({ name, mode, config }) => { export const createApp: Fetcher<AppDetailResponse, { name: string; icon: string, icon_background: string, mode: AppMode; config?: ModelConfig }> = ({ name, icon, icon_background, mode, config }) => {
return post('apps', { body: { name, mode, model_config: config } }) as Promise<AppDetailResponse> return post('apps', { body: { name, icon, icon_background, mode, model_config: config } }) as Promise<AppDetailResponse>
} }
export const deleteApp: Fetcher<CommonResponse, string> = (appID) => { export const deleteApp: Fetcher<CommonResponse, string> = (appID) => {
......
...@@ -190,6 +190,12 @@ export type App = { ...@@ -190,6 +190,12 @@ export type App = {
id: string id: string
/** Name */ /** Name */
name: string name: string
/** Icon */
icon: string
/** Icon Background */
icon_background: string
/** Mode */ /** Mode */
mode: AppMode mode: AppMode
/** Enable web app */ /** Enable web app */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment