Commit ff61570b authored by Gillian97's avatar Gillian97

feat: update app name icon

parent 14147660
...@@ -5,59 +5,167 @@ import Link from 'next/link' ...@@ -5,59 +5,167 @@ import Link from 'next/link'
import type { MouseEventHandler } from 'react' import type { MouseEventHandler } from 'react'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import style from '../list.module.css' import style from '../list.module.css'
import AppModeLabel from './AppModeLabel' import AppModeLabel from './AppModeLabel'
import AppSettings from './AppSettings'
import s from './style.module.css'
import type { App } from '@/types/app' import type { App } from '@/types/app'
import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import { deleteApp } from '@/service/apps' import { deleteApp, updateAppIcon, updateAppName } 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 CustomPopover from '@/app/components/base/popover'
import Divider from '@/app/components/base/divider'
export type AppCardProps = { export type AppCardProps = {
app: App app: App
onDelete?: () => void onRefresh?: () => void
} }
const AppCard = ({ const AppCard = ({ app, onRefresh }: AppCardProps) => {
app,
onDelete,
}: AppCardProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const mutateApps = useContextSelector(AppsContext, state => state.mutateApps) const mutateApps = useContextSelector(
AppsContext,
state => state.mutateApps,
)
const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const onDeleteClick: MouseEventHandler = useCallback((e) => { const [showSettingsModal, setShowSettingsModal] = useState(false)
const [updateLoading, setUpdateLoading] = useState(false)
const onClickSettings: MouseEventHandler = useCallback((e) => {
e.preventDefault()
setShowSettingsModal(true)
}, [])
const onClickDelete: MouseEventHandler = useCallback((e) => {
e.preventDefault() e.preventDefault()
setShowConfirmDelete(true) setShowConfirmDelete(true)
}, []) }, [])
const onConfirmDelete = useCallback(async () => { const onConfirmDelete = useCallback(async () => {
try { try {
await deleteApp(app.id) await deleteApp(app.id)
notify({ type: 'success', message: t('app.appDeleted') }) notify({ type: 'success', message: t('app.appDeleted') })
if (onDelete) if (onRefresh)
onDelete() onRefresh()
mutateApps() mutateApps()
} }
catch (e: any) { catch (e: any) {
notify({ type: 'error', message: `${t('app.appDeleteFailed')}${'message' in e ? `: ${e.message}` : ''}` }) notify({
type: 'error',
message: `${t('app.appDeleteFailed')}${
'message' in e ? `: ${e.message}` : ''
}`,
})
} }
setShowConfirmDelete(false) setShowConfirmDelete(false)
}, [app.id]) }, [app.id])
const onUpdate = useCallback(
async (params: { name: App['name']; iconInfo: { icon: string;icon_background: string } }) => {
const reqList = []
if (params.name !== app.name) {
reqList.push(updateAppName({
url: `/apps/${app.id}/name`,
body: { name: params.name },
}))
}
if (params.iconInfo.icon !== app.icon || params.iconInfo.icon_background !== app.icon_background)
reqList.push(updateAppIcon({ url: `/apps/${app.id}/icon`, body: params.iconInfo }))
if (!reqList.length) {
setShowSettingsModal(false)
notify({
type: 'info',
message: t('common.actionMsg.noModification'),
})
return
}
setUpdateLoading(true)
const updateRes = await Promise.allSettled(reqList)
if (!updateRes.find(v => v.status === 'rejected')) {
notify({
type: 'success',
message: t('common.actionMsg.modifiedSuccessfully'),
})
setShowSettingsModal(false)
}
else {
notify({
type: 'error',
message: t('common.actionMsg.modificationFailed'),
})
}
if (onRefresh)
onRefresh()
mutateApps()
setUpdateLoading(false)
},
[app.id],
)
const Operations = (props: any) => {
return <div className="w-full py-1">
<div className={s.actionItem} onClick={(e) => {
props?.onClose()
onClickSettings(e)
}}>
<span className={s.actionName}>
{t('common.operation.settings')}
</span>
</div>
<Divider className="my-1" />
<div
className={cn(s.actionItem, s.deleteActionItem, 'group')}
onClick={(e) => {
props?.onClose()
onClickDelete(e)
}}
>
<span
className={cn(s.actionName, 'group-hover:text-red-500')}
>
{t('common.operation.delete')}
</span>
</div>
</div>
}
return ( return (
<> <>
<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' icon={app.icon} background={app.icon_background} /> <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>
<span className={style.deleteAppIcon} onClick={onDeleteClick} /> <CustomPopover
htmlContent={
<Operations />
}
position="br"
btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />}
btnClassName={open =>
cn(
open ? '!bg-gray-100 !shadow-none' : '!bg-transparent',
style.actionIconWrapper,
)
}
className={'!w-[128px] h-fit !z-20'}
/>
</div>
<div className={style.listItemDescription}>
{app.model_config?.pre_prompt}
</div> </div>
<div className={style.listItemDescription}>{app.model_config?.pre_prompt}</div>
<div className={style.listItemFooter}> <div className={style.listItemFooter}>
<AppModeLabel mode={app.mode} /> <AppModeLabel mode={app.mode} />
</div> </div>
...@@ -72,6 +180,16 @@ const AppCard = ({ ...@@ -72,6 +180,16 @@ const AppCard = ({
onCancel={() => setShowConfirmDelete(false)} onCancel={() => setShowConfirmDelete(false)}
/> />
)} )}
{showSettingsModal && (
<AppSettings
isShow={showSettingsModal}
onClose={() => setShowSettingsModal(false)}
appName={app.name}
appIcon={{ icon: app.icon, icon_background: app.icon_background }}
onUpdate={onUpdate}
loading={updateLoading}
/>
)}
</Link> </Link>
</> </>
) )
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { type AppMode } from '@/types/app'
import style from '../list.module.css' import style from '../list.module.css'
import { type AppMode } from '@/types/app'
export type AppModeLabelProps = { export type AppModeLabelProps = {
mode: AppMode mode: AppMode
......
'use client'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import style from '../list.module.css'
import Dialog from '@/app/components/base/dialog'
import Button from '@/app/components/base/button'
import AppIcon from '@/app/components/base/app-icon'
import EmojiPicker from '@/app/components/base/emoji-picker'
import type { App } from '@/types/app'
type Props = {
isShow: boolean
onClose: () => void
appIcon: { icon: App['icon']; icon_background: App['icon_background'] }
appName: App['name']
onUpdate: (v: any) => void
loading: boolean
}
const AppSettings = (props: Props) => {
const { isShow, onClose, appIcon, appName, onUpdate, loading } = props
const { t } = useTranslation()
// Emoji Picker
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
const [emoji, setEmoji] = useState(appIcon)
// input name
const [inputName, setInputName] = useState(appName)
return (
<>
{showEmojiPicker && (
<EmojiPicker
onSelect={(icon, icon_background) => {
setEmoji({ icon, icon_background })
setShowEmojiPicker(false)
}}
onClose={() => {
setEmoji({ icon: '🤖', icon_background: '#FFEAD5' })
setShowEmojiPicker(false)
}}
/>
)}
<Dialog
show={isShow}
title={t('app.editApp.startToEdit')}
footer={
<>
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
<Button type="primary" loading={loading} onClick={() => onUpdate({ name: inputName, iconInfo: emoji })}>
{t('common.operation.save')}
</Button>
</>
}
>
<h3 className={style.newItemCaption}>{t('explore.appCustomize.subTitle')}</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
placeholder={t('app.appNamePlaceholder') || ''}
onChange={e => setInputName(e.target.value)}
value={inputName}
className="h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow"
/>
</div>
</Dialog>
</>
)
}
export default AppSettings
...@@ -53,7 +53,7 @@ const Apps = () => { ...@@ -53,7 +53,7 @@ const Apps = () => {
return ( 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'> <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'>
{data?.map(({ data: apps }) => apps.map(app => ( {data?.map(({ data: apps }) => apps.map(app => (
<AppCard key={app.id} app={app} onDelete={mutate} /> <AppCard key={app.id} app={app} onRefresh={mutate} />
)))} )))}
<NewAppCard ref={anchorRef} onSuccess={mutate} /> <NewAppCard ref={anchorRef} onSuccess={mutate} />
</nav> </nav>
......
...@@ -119,7 +119,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => { ...@@ -119,7 +119,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
<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' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} /> <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' placeholder={t('app.appNamePlaceholder') || ''}/>
</div> </div>
<div className='h-[247px]'> <div className='h-[247px]'>
......
.commonIcon {
@apply w-4 h-4 inline-block align-middle;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
}
.actionIcon {
@apply bg-gray-500;
mask-image: url(~@/assets/action.svg);
}
.actionItem {
@apply h-9 py-2 px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer;
}
.deleteActionItem {
@apply hover:bg-red-50 !important;
}
.actionName {
@apply text-gray-700 text-sm;
}
...@@ -14,9 +14,14 @@ ...@@ -14,9 +14,14 @@
@apply relative; @apply relative;
} }
.listItem.selectable::before { .listItem.selectable::before {
content: ''; 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; @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, rgba(235, 245, 255, 0.5), rgba(235, 245, 255, 0.5)), #FFFFFF; background: linear-gradient(
0deg,
rgba(235, 245, 255, 0.5),
rgba(235, 245, 255, 0.5)
),
#ffffff;
} }
.listItem.selectable:hover::before { .listItem.selectable:hover::before {
@apply opacity-100; @apply opacity-100;
...@@ -65,13 +70,13 @@ ...@@ -65,13 +70,13 @@
@apply text-primary-600; @apply text-primary-600;
} }
.newItemIconAdd { .newItemIconAdd {
background-image: url('./apps/assets/add.svg'); background-image: url("./apps/assets/add.svg");
} }
.newItemIconChat { .newItemIconChat {
background-image: url('./apps/assets/chat.svg'); background-image: url("./apps/assets/chat.svg");
} }
.newItemIconComplete { .newItemIconComplete {
background-image: url('./apps/assets/completion.svg'); background-image: url("./apps/assets/completion.svg");
} }
.listItemTitle { .listItemTitle {
...@@ -86,13 +91,11 @@ ...@@ -86,13 +91,11 @@
@apply absolute top-0 left-0 w-full h-full overflow-hidden text-ellipsis whitespace-nowrap; @apply absolute top-0 left-0 w-full h-full overflow-hidden text-ellipsis whitespace-nowrap;
} }
.deleteAppIcon { .actionIconWrapper {
@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; @apply hidden h-8 w-8 p-2 rounded-md border-none hover:bg-gray-100 !important;
background-size: 16px;
background-image: url('./apps/assets/delete.svg');
} }
.listItem:hover .deleteAppIcon { .listItem:hover .actionIconWrapper {
@apply block; @apply !inline-flex;
} }
.listItemDescription { .listItemDescription {
...@@ -114,19 +117,19 @@ ...@@ -114,19 +117,19 @@
@apply block w-3 h-3 bg-center bg-contain; @apply block w-3 h-3 bg-center bg-contain;
} }
.solidChatIcon { .solidChatIcon {
background-image: url('./apps/assets/chat-solid.svg'); background-image: url("./apps/assets/chat-solid.svg");
} }
.solidCompletionIcon { .solidCompletionIcon {
background-image: url('./apps/assets/completion-solid.svg'); background-image: url("./apps/assets/completion-solid.svg");
} }
.docIcon { .docIcon {
background-image: url('./datasets/assets/doc.svg'); background-image: url("./datasets/assets/doc.svg");
} }
.textIcon { .textIcon {
background-image: url('./datasets/assets/text.svg'); background-image: url("./datasets/assets/text.svg");
} }
.applicationIcon { .applicationIcon {
background-image: url('./datasets/assets/application.svg'); background-image: url("./datasets/assets/application.svg");
} }
.newItemCardHeading { .newItemCardHeading {
...@@ -140,24 +143,24 @@ ...@@ -140,24 +143,24 @@
@apply inline-flex items-center gap-1 text-xs text-gray-400 transition-colors duration-200 ease-in-out; @apply inline-flex items-center gap-1 text-xs text-gray-400 transition-colors duration-200 ease-in-out;
} }
.listItem:hover .listItemLink { .listItem:hover .listItemLink {
@apply text-primary-600 @apply text-primary-600;
} }
.linkIcon { .linkIcon {
@apply block w-[13px] h-[13px] bg-center bg-contain; @apply block w-[13px] h-[13px] bg-center bg-contain;
background-image: url('./apps/assets/link.svg'); background-image: url("./apps/assets/link.svg");
} }
.linkIcon.grayLinkIcon { .linkIcon.grayLinkIcon {
background-image: url('./apps/assets/link-gray.svg'); background-image: url("./apps/assets/link-gray.svg");
} }
.listItem:hover .grayLinkIcon { .listItem:hover .grayLinkIcon {
background-image: url('./apps/assets/link.svg'); background-image: url("./apps/assets/link.svg");
} }
.rightIcon { .rightIcon {
@apply block w-[13px] h-[13px] bg-center bg-contain; @apply block w-[13px] h-[13px] bg-center bg-contain;
background-image: url('./apps/assets/right-arrow.svg'); background-image: url("./apps/assets/right-arrow.svg");
} }
.socialMediaLink { .socialMediaLink {
...@@ -169,11 +172,11 @@ ...@@ -169,11 +172,11 @@
} }
.githubIcon { .githubIcon {
background-image: url('./apps/assets/github.svg'); background-image: url("./apps/assets/github.svg");
} }
.discordIcon { .discordIcon {
background-image: url('./apps/assets/discord.svg'); background-image: url("./apps/assets/discord.svg");
} }
/* #region new app dialog */ /* #region new app dialog */
......
...@@ -41,8 +41,8 @@ const ConfirmUI: FC<IConfirmUIProps> = ({ ...@@ -41,8 +41,8 @@ const ConfirmUI: FC<IConfirmUIProps> = ({
</div> </div>
<div className='flex gap-3 mt-4 ml-12'> <div className='flex gap-3 mt-4 ml-12'>
<div onClick={onConfirm} className='w-20 leading-9 text-center text-white border rounded-lg cursor-pointer h-9 border-color-primary-700 bg-primary-700'>{confirmText || t('common.operation.confirm')}</div> <div onClick={onConfirm} className='w-20 leading-[34px] text-center text-white border rounded-lg cursor-pointer h-9 border-color-primary-700 bg-primary-700'>{confirmText || t('common.operation.confirm')}</div>
<div onClick={onCancel} className='w-20 leading-9 text-center text-gray-500 border rounded-lg cursor-pointer h-9 border-color-gray-200'>{cancelText || t('common.operation.cancel')}</div> <div onClick={onCancel} className='w-20 leading-[34px] text-center text-gray-500 border rounded-lg cursor-pointer h-9 border-color-gray-200'>{cancelText || t('common.operation.cancel')}</div>
</div> </div>
</div> </div>
......
import { Dialog, Transition } from '@headlessui/react' import { Dialog, Transition } from '@headlessui/react'
import { Fragment } from 'react' import { Fragment } from 'react'
import ConfirmUI from '../confirm-ui'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ConfirmUI from '../confirm-ui'
// https://headlessui.com/react/dialog // https://headlessui.com/react/dialog
......
...@@ -46,47 +46,49 @@ export default function CustomPopover({ ...@@ -46,47 +46,49 @@ export default function CustomPopover({
: { : {
onMouseLeave: () => onMouseLeave(open), onMouseLeave: () => onMouseLeave(open),
onMouseEnter: () => onMouseEnter(open), onMouseEnter: () => onMouseEnter(open),
}) })}
}
> >
<Popover.Button <Popover.Button
ref={buttonRef} ref={buttonRef}
className={`group ${s.popupBtn} ${open ? '' : 'bg-gray-100'} ${!btnClassName ? '' : typeof btnClassName === 'string' ? btnClassName : btnClassName?.(open)}`} className={`group ${s.popupBtn} ${open ? '' : 'bg-gray-100'} ${
!btnClassName
? ''
: typeof btnClassName === 'string'
? btnClassName
: btnClassName?.(open)
}`}
> >
{btnElement} {btnElement}
</Popover.Button> </Popover.Button>
<Transition <Transition as={Fragment}>
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel <Popover.Panel
className={`${s.popupPanel} ${position === 'br' ? 'right-0' : 'transform -translate-x-1/2 left-1/2'} ${className}`} className={`${s.popupPanel} ${
position === 'br'
? 'right-0'
: 'transform -translate-x-1/2 left-1/2'
} ${className}`}
{...(trigger !== 'hover' {...(trigger !== 'hover'
? {} ? {}
: { : {
onMouseLeave: () => onMouseLeave(open), onMouseLeave: () => onMouseLeave(open),
onMouseEnter: () => onMouseEnter(open), onMouseEnter: () => onMouseEnter(open),
})
}>
<div
className={s.panelContainer}
{...(trigger !== 'hover'
? {}
: {
onMouseLeave: () => onMouseLeave(open),
onMouseEnter: () => onMouseEnter(open),
})
}
>
{cloneElement(htmlContent as React.ReactElement, {
onClose: () => onMouseLeave(open),
})} })}
</div> >
{({ close }) => (
<div
className={s.panelContainer}
{...(trigger !== 'hover'
? {}
: {
onMouseLeave: () => onMouseLeave(open),
onMouseEnter: () => onMouseEnter(open),
})}
>
{cloneElement(htmlContent as React.ReactElement, {
onClose: () => close(),
})}
</div>
)}
</Popover.Panel> </Popover.Panel>
</Transition> </Transition>
</div> </div>
......
...@@ -138,10 +138,74 @@ export const OperationAction: FC<{ ...@@ -138,10 +138,74 @@ export const OperationAction: FC<{
onUpdate(operationName) onUpdate(operationName)
} }
return <div const Operations = (props: any) => <div className='w-full py-1'>
className='flex items-center' {!isListScene && <>
onClick={e => e.stopPropagation()} <div className='flex justify-between items-center mx-4 pt-2'>
> <span className={cn(s.actionName, 'font-medium')}>
{!archived && enabled ? t('datasetDocuments.list.index.enable') : t('datasetDocuments.list.index.disable')}
</span>
<Tooltip
selector={`detail-switch-${id}`}
content={t('datasetDocuments.list.action.enableWarning') as string}
className='!font-semibold'
disabled={!archived}
>
<div>
<Switch
defaultValue={archived ? false : enabled}
onChange={v => !archived && onOperate(v ? 'enable' : 'disable')}
disabled={archived}
size='md'
/>
</div>
</Tooltip>
</div>
<div className='mx-4 pb-1 pt-0.5 text-xs text-gray-500'>
{!archived && enabled ? t('datasetDocuments.list.index.enableTip') : t('datasetDocuments.list.index.disableTip')}
</div>
<Divider />
</>}
{!archived && (
<>
<div className={s.actionItem} onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
<SettingsIcon />
<span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
</div>
{
!isListScene && (
<div className={s.actionItem} onClick={showNewSegmentModal}>
<FilePlus02 className='w-4 h-4 text-gray-500' />
<span className={s.actionName}>{t('datasetDocuments.list.action.add')}</span>
</div>
)
}
{
data_source_type === 'notion_import' && (
<div className={s.actionItem} onClick={() => onOperate('sync')}>
<SyncIcon />
<span className={s.actionName}>{t('datasetDocuments.list.action.sync')}</span>
</div>
)
}
<Divider className='my-1' />
</>
)}
{!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}>
<ArchiveIcon />
<span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span>
</div>}
<div
className={cn(s.actionItem, s.deleteActionItem, 'group')}
onClick={() => {
setShowModal(true)
props?.onClose()
}}>
<TrashIcon className={'w-4 h-4 stroke-current text-gray-500 stroke-2 group-hover:text-red-500'} />
<span className={cn(s.actionName, 'group-hover:text-red-500')}>{t('datasetDocuments.list.action.delete')}</span>
</div>
</div>
return <div className='flex items-center' onClick={e => e.stopPropagation()}>
{isListScene && <> {isListScene && <>
{archived {archived
? <Tooltip selector={`list-switch-${id}`} content={t('datasetDocuments.list.action.enableWarning') as string} className='!font-semibold'> ? <Tooltip selector={`list-switch-${id}`} content={t('datasetDocuments.list.action.enableWarning') as string} className='!font-semibold'>
...@@ -154,69 +218,7 @@ export const OperationAction: FC<{ ...@@ -154,69 +218,7 @@ export const OperationAction: FC<{
<Divider className='!ml-4 !mr-2 !h-3' type='vertical' /> <Divider className='!ml-4 !mr-2 !h-3' type='vertical' />
</>} </>}
<Popover <Popover
htmlContent={ htmlContent={<Operations />}
<div className='w-full py-1'>
{!isListScene && <>
<div className='flex justify-between items-center mx-4 pt-2'>
<span className={cn(s.actionName, 'font-medium')}>
{!archived && enabled ? t('datasetDocuments.list.index.enable') : t('datasetDocuments.list.index.disable')}
</span>
<Tooltip
selector={`detail-switch-${id}`}
content={t('datasetDocuments.list.action.enableWarning') as string}
className='!font-semibold'
disabled={!archived}
>
<div>
<Switch
defaultValue={archived ? false : enabled}
onChange={v => !archived && onOperate(v ? 'enable' : 'disable')}
disabled={archived}
size='md'
/>
</div>
</Tooltip>
</div>
<div className='mx-4 pb-1 pt-0.5 text-xs text-gray-500'>
{!archived && enabled ? t('datasetDocuments.list.index.enableTip') : t('datasetDocuments.list.index.disableTip')}
</div>
<Divider />
</>}
{!archived && (
<>
<div className={s.actionItem} onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
<SettingsIcon />
<span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
</div>
{
!isListScene && (
<div className={s.actionItem} onClick={showNewSegmentModal}>
<FilePlus02 className='w-4 h-4 text-gray-500' />
<span className={s.actionName}>{t('datasetDocuments.list.action.add')}</span>
</div>
)
}
{
data_source_type === 'notion_import' && (
<div className={s.actionItem} onClick={() => onOperate('sync')}>
<SyncIcon />
<span className={s.actionName}>{t('datasetDocuments.list.action.sync')}</span>
</div>
)
}
<Divider className='my-1' />
</>
)}
{!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}>
<ArchiveIcon />
<span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span>
</div>}
<div className={cn(s.actionItem, s.deleteActionItem, 'group')} onClick={() => setShowModal(true)}>
<TrashIcon className={'w-4 h-4 stroke-current text-gray-500 stroke-2 group-hover:text-red-500'} />
<span className={cn(s.actionName, 'group-hover:text-red-500')}>{t('datasetDocuments.list.action.delete')}</span>
</div>
</div>
}
trigger='click' trigger='click'
position='br' position='br'
btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />} btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />}
......
...@@ -58,34 +58,34 @@ ...@@ -58,34 +58,34 @@
} }
.actionIcon { .actionIcon {
@apply bg-gray-500; @apply bg-gray-500;
mask-image: url(./assets/action.svg); mask-image: url(~@/assets/action.svg);
} }
.pdfIcon { .pdfIcon {
background-image: url(./assets/pdf.svg); background-image: url(~@/assets/pdf.svg);
} }
.jsonIcon { .jsonIcon {
background-image: url(./assets/json.svg); background-image: url(~@/assets/json.svg);
} }
.htmlIcon { .htmlIcon {
background-image: url(./assets/html.svg); background-image: url(~@/assets/html.svg);
} }
.txtIcon { .txtIcon {
background-image: url(./assets/txt.svg); background-image: url(~@/assets/txt.svg);
} }
.markdownIcon { .markdownIcon {
background-image: url(./assets/md.svg); background-image: url(~@/assets/md.svg);
} }
.mdIcon { .mdIcon {
background-image: url(./assets/md.svg); background-image: url(~@/assets/md.svg);
} }
.xlsIcon { .xlsIcon {
background-image: url(./assets/xlsx.svg); background-image: url(~@/assets/xlsx.svg);
} }
.xlsxIcon { .xlsxIcon {
background-image: url(./assets/xlsx.svg); background-image: url(~@/assets/xlsx.svg);
} }
.csvIcon { .csvIcon {
background-image: url(./assets/csv.svg); background-image: url(~@/assets/csv.svg);
} }
.statusItemDetail { .statusItemDetail {
@apply h-8 font-medium border border-gray-200 inline-flex items-center rounded-lg pl-3 pr-4 mr-2; @apply h-8 font-medium border border-gray-200 inline-flex items-center rounded-lg pl-3 pr-4 mr-2;
......
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
.actionIcon { .actionIcon {
@apply bg-gray-500; @apply bg-gray-500;
mask-image: url(~@/app/components/datasets/documents/assets/action.svg); mask-image: url(~@/assets/action.svg);
} }
body .btn { body .btn {
background: url(~@/app/components/datasets/documents/assets/action.svg) center center no-repeat transparent; background: url(~@/assets/action.svg) center center no-repeat transparent;
background-size: 16px 16px; background-size: 16px 16px;
/* mask-image: ; */ /* mask-image: ; */
} }
...@@ -32,4 +32,4 @@ body .btn:hover { ...@@ -32,4 +32,4 @@ body .btn:hover {
.deleteActionItem:hover .deleteActionItemChild { .deleteActionItem:hover .deleteActionItemChild {
@apply text-red-500; @apply text-red-500;
} }
\ No newline at end of file
.btn { .btn {
background: url(~@/app/components/datasets/documents/assets/action.svg) center center no-repeat transparent; background: url(~@/assets/action.svg) center center no-repeat transparent;
background-size: 16px 16px; background-size: 16px 16px;
/* mask-image: ; */ /* mask-image: ; */
} }
.panelBorder { .panelBorder {
border: 0.5px solid rgba(0, 0, 0, .05); border: 0.5px solid rgba(0, 0, 0, .05);
} }
\ No newline at end of file
...@@ -15,6 +15,7 @@ const translation = { ...@@ -15,6 +15,7 @@ const translation = {
communityIntro: communityIntro:
'Discuss with team members, contributors and developers on different channels.', 'Discuss with team members, contributors and developers on different channels.',
roadmap: 'See our roadmap', roadmap: 'See our roadmap',
appNamePlaceholder: 'Please enter the name of the app',
newApp: { newApp: {
startToCreate: 'Let\'s start with your new app', startToCreate: 'Let\'s start with your new app',
captionName: 'Give your app a name', captionName: 'Give your app a name',
...@@ -36,6 +37,9 @@ const translation = { ...@@ -36,6 +37,9 @@ const translation = {
appCreated: 'App created', appCreated: 'App created',
appCreateFailed: 'Failed to create app', appCreateFailed: 'Failed to create app',
}, },
editApp: {
startToEdit: 'Edit App',
},
emoji: { emoji: {
ok: 'OK', ok: 'OK',
cancel: 'Cancel', cancel: 'Cancel',
......
...@@ -14,6 +14,7 @@ const translation = { ...@@ -14,6 +14,7 @@ const translation = {
join: '参与社区', join: '参与社区',
communityIntro: '与团队成员、贡献者和开发者在不同频道中交流', communityIntro: '与团队成员、贡献者和开发者在不同频道中交流',
roadmap: '产品路线图', roadmap: '产品路线图',
appNamePlaceholder: '请输入应用名称',
newApp: { newApp: {
startToCreate: '开始创建一个新应用', startToCreate: '开始创建一个新应用',
captionName: '给应用起个名字', captionName: '给应用起个名字',
...@@ -35,6 +36,9 @@ const translation = { ...@@ -35,6 +36,9 @@ const translation = {
appCreated: '应用已创建', appCreated: '应用已创建',
appCreateFailed: '应用创建失败', appCreateFailed: '应用创建失败',
}, },
editApp: {
startToEdit: '编辑应用',
},
emoji: { emoji: {
ok: '确认', ok: '确认',
cancel: '取消', cancel: '取消',
......
...@@ -23,6 +23,8 @@ const translation = { ...@@ -23,6 +23,8 @@ const translation = {
lineBreak: 'Line break', lineBreak: 'Line break',
sure: 'I\'m sure', sure: 'I\'m sure',
download: 'Download', download: 'Download',
delete: 'Delete',
settings: 'Settings',
}, },
placeholder: { placeholder: {
input: 'Please enter', input: 'Please enter',
...@@ -32,6 +34,7 @@ const translation = { ...@@ -32,6 +34,7 @@ const translation = {
char: 'chars', char: 'chars',
}, },
actionMsg: { actionMsg: {
noModification: 'No modifications at the moment.',
modifiedSuccessfully: 'Modified successfully', modifiedSuccessfully: 'Modified successfully',
modificationFailed: 'Modification failed', modificationFailed: 'Modification failed',
copySuccessfully: 'Copied successfully', copySuccessfully: 'Copied successfully',
......
...@@ -23,6 +23,8 @@ const translation = { ...@@ -23,6 +23,8 @@ const translation = {
lineBreak: '换行', lineBreak: '换行',
sure: '我确定', sure: '我确定',
download: '下载', download: '下载',
delete: '删除',
settings: '设置',
}, },
placeholder: { placeholder: {
input: '请输入', input: '请输入',
...@@ -32,6 +34,7 @@ const translation = { ...@@ -32,6 +34,7 @@ const translation = {
char: '个字符', char: '个字符',
}, },
actionMsg: { actionMsg: {
noModification: '暂无修改',
modifiedSuccessfully: '修改成功', modifiedSuccessfully: '修改成功',
modificationFailed: '修改失败', modificationFailed: '修改失败',
copySuccessfully: '复制成功', copySuccessfully: '复制成功',
......
...@@ -77,6 +77,8 @@ export type CreateAppResponse = App ...@@ -77,6 +77,8 @@ export type CreateAppResponse = App
export type UpdateAppNameResponse = App export type UpdateAppNameResponse = App
export type UpdateAppIconResponse = App
export type UpdateAppSiteCodeResponse = { app_id: string } & SiteConfig export type UpdateAppSiteCodeResponse = { app_id: string } & SiteConfig
export type AppDailyConversationsResponse = { export type AppDailyConversationsResponse = {
......
...@@ -136,6 +136,7 @@ export type DataSourceInfo = { ...@@ -136,6 +136,7 @@ export type DataSourceInfo = {
created_by: string created_by: string
extension: string extension: string
} }
notion_page_icon?: string
} }
export type InitialDocumentDetail = { export type InitialDocumentDetail = {
......
import type { Fetcher } from 'swr' import type { Fetcher } from 'swr'
import { del, get, post } from './base' import { del, get, post } from './base'
import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, CreateApiKeyResponse, GenerationIntroductionResponse, UpdateAppModelConfigResponse, UpdateAppNameResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse } from '@/models/app' import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, CreateApiKeyResponse, GenerationIntroductionResponse, UpdateAppIconResponse, UpdateAppModelConfigResponse, UpdateAppNameResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse } from '@/models/app'
import type { CommonResponse } from '@/models/common' import type { CommonResponse } from '@/models/common'
import type { AppMode, ModelConfig } from '@/types/app' import type { AppMode, ModelConfig } from '@/types/app'
...@@ -24,8 +24,13 @@ export const deleteApp: Fetcher<CommonResponse, string> = (appID) => { ...@@ -24,8 +24,13 @@ export const deleteApp: Fetcher<CommonResponse, string> = (appID) => {
return del(`apps/${appID}`) as Promise<CommonResponse> return del(`apps/${appID}`) as Promise<CommonResponse>
} }
// path: /apps/{appId}/icon
export const updateAppIcon: Fetcher<UpdateAppIconResponse, { url: string; body: { icon: string; icon_background: string } }> = ({ url, body }) => {
return post(url, { body }) as Promise<UpdateAppIconResponse>
}
// path: /apps/{appId}/name // path: /apps/{appId}/name
export const updateAppName: Fetcher<UpdateAppNameResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => { export const updateAppName: Fetcher<UpdateAppNameResponse, { url: string; body: { name: string } }> = ({ url, body }) => {
return post(url, { body }) as Promise<UpdateAppNameResponse> return post(url, { body }) as Promise<UpdateAppNameResponse>
} }
......
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