Commit ff61570b authored by Gillian97's avatar Gillian97

feat: update app name icon

parent 14147660
......@@ -5,59 +5,167 @@ import Link from 'next/link'
import type { MouseEventHandler } from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import style from '../list.module.css'
import AppModeLabel from './AppModeLabel'
import AppSettings from './AppSettings'
import s from './style.module.css'
import type { App } from '@/types/app'
import Confirm from '@/app/components/base/confirm'
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 AppsContext from '@/context/app-context'
import CustomPopover from '@/app/components/base/popover'
import Divider from '@/app/components/base/divider'
export type AppCardProps = {
app: App
onDelete?: () => void
onRefresh?: () => void
}
const AppCard = ({
app,
onDelete,
}: AppCardProps) => {
const AppCard = ({ app, onRefresh }: AppCardProps) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const mutateApps = useContextSelector(AppsContext, state => state.mutateApps)
const mutateApps = useContextSelector(
AppsContext,
state => state.mutateApps,
)
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()
setShowConfirmDelete(true)
}, [])
const onConfirmDelete = useCallback(async () => {
try {
await deleteApp(app.id)
notify({ type: 'success', message: t('app.appDeleted') })
if (onDelete)
onDelete()
if (onRefresh)
onRefresh()
mutateApps()
}
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)
}, [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 (
<>
<Link href={`/app/${app.id}/overview`} className={style.listItem}>
<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.listItemHeadingContent}>{app.name}</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 className={style.listItemDescription}>{app.model_config?.pre_prompt}</div>
<div className={style.listItemFooter}>
<AppModeLabel mode={app.mode} />
</div>
......@@ -72,6 +180,16 @@ const AppCard = ({
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>
</>
)
......
......@@ -2,8 +2,8 @@
import classNames from 'classnames'
import { useTranslation } from 'react-i18next'
import { type AppMode } from '@/types/app'
import style from '../list.module.css'
import { type AppMode } from '@/types/app'
export type AppModeLabelProps = {
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 = () => {
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'>
{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} />
</nav>
......
......@@ -119,7 +119,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
<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' />
<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]'>
......
.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 @@
@apply relative;
}
.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;
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 {
@apply opacity-100;
......@@ -65,13 +70,13 @@
@apply text-primary-600;
}
.newItemIconAdd {
background-image: url('./apps/assets/add.svg');
background-image: url("./apps/assets/add.svg");
}
.newItemIconChat {
background-image: url('./apps/assets/chat.svg');
background-image: url("./apps/assets/chat.svg");
}
.newItemIconComplete {
background-image: url('./apps/assets/completion.svg');
background-image: url("./apps/assets/completion.svg");
}
.listItemTitle {
......@@ -86,13 +91,11 @@
@apply absolute top-0 left-0 w-full h-full overflow-hidden text-ellipsis whitespace-nowrap;
}
.deleteAppIcon {
@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('./apps/assets/delete.svg');
.actionIconWrapper {
@apply hidden h-8 w-8 p-2 rounded-md border-none hover:bg-gray-100 !important;
}
.listItem:hover .deleteAppIcon {
@apply block;
.listItem:hover .actionIconWrapper {
@apply !inline-flex;
}
.listItemDescription {
......@@ -114,19 +117,19 @@
@apply block w-3 h-3 bg-center bg-contain;
}
.solidChatIcon {
background-image: url('./apps/assets/chat-solid.svg');
background-image: url("./apps/assets/chat-solid.svg");
}
.solidCompletionIcon {
background-image: url('./apps/assets/completion-solid.svg');
background-image: url("./apps/assets/completion-solid.svg");
}
.docIcon {
background-image: url('./datasets/assets/doc.svg');
background-image: url("./datasets/assets/doc.svg");
}
.textIcon {
background-image: url('./datasets/assets/text.svg');
background-image: url("./datasets/assets/text.svg");
}
.applicationIcon {
background-image: url('./datasets/assets/application.svg');
background-image: url("./datasets/assets/application.svg");
}
.newItemCardHeading {
......@@ -140,24 +143,24 @@
@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
@apply text-primary-600;
}
.linkIcon {
@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 {
background-image: url('./apps/assets/link-gray.svg');
background-image: url("./apps/assets/link-gray.svg");
}
.listItem:hover .grayLinkIcon {
background-image: url('./apps/assets/link.svg');
background-image: url("./apps/assets/link.svg");
}
.rightIcon {
@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 {
......@@ -169,11 +172,11 @@
}
.githubIcon {
background-image: url('./apps/assets/github.svg');
background-image: url("./apps/assets/github.svg");
}
.discordIcon {
background-image: url('./apps/assets/discord.svg');
background-image: url("./apps/assets/discord.svg");
}
/* #region new app dialog */
......
......@@ -41,8 +41,8 @@ const ConfirmUI: FC<IConfirmUIProps> = ({
</div>
<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={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={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-[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>
......
import { Dialog, Transition } from '@headlessui/react'
import { Fragment } from 'react'
import ConfirmUI from '../confirm-ui'
import { useTranslation } from 'react-i18next'
import ConfirmUI from '../confirm-ui'
// https://headlessui.com/react/dialog
......
......@@ -46,47 +46,49 @@ export default function CustomPopover({
: {
onMouseLeave: () => onMouseLeave(open),
onMouseEnter: () => onMouseEnter(open),
})
}
})}
>
<Popover.Button
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}
</Popover.Button>
<Transition
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"
>
<Transition as={Fragment}>
<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'
? {}
: {
onMouseLeave: () => onMouseLeave(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>
</Transition>
</div>
......
......@@ -138,10 +138,74 @@ export const OperationAction: FC<{
onUpdate(operationName)
}
return <div
className='flex items-center'
onClick={e => e.stopPropagation()}
>
const Operations = (props: any) => <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)
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 && <>
{archived
? <Tooltip selector={`list-switch-${id}`} content={t('datasetDocuments.list.action.enableWarning') as string} className='!font-semibold'>
......@@ -154,69 +218,7 @@ export const OperationAction: FC<{
<Divider className='!ml-4 !mr-2 !h-3' type='vertical' />
</>}
<Popover
htmlContent={
<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>
}
htmlContent={<Operations />}
trigger='click'
position='br'
btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />}
......
......@@ -58,34 +58,34 @@
}
.actionIcon {
@apply bg-gray-500;
mask-image: url(./assets/action.svg);
mask-image: url(~@/assets/action.svg);
}
.pdfIcon {
background-image: url(./assets/pdf.svg);
background-image: url(~@/assets/pdf.svg);
}
.jsonIcon {
background-image: url(./assets/json.svg);
background-image: url(~@/assets/json.svg);
}
.htmlIcon {
background-image: url(./assets/html.svg);
background-image: url(~@/assets/html.svg);
}
.txtIcon {
background-image: url(./assets/txt.svg);
background-image: url(~@/assets/txt.svg);
}
.markdownIcon {
background-image: url(./assets/md.svg);
background-image: url(~@/assets/md.svg);
}
.mdIcon {
background-image: url(./assets/md.svg);
background-image: url(~@/assets/md.svg);
}
.xlsIcon {
background-image: url(./assets/xlsx.svg);
background-image: url(~@/assets/xlsx.svg);
}
.xlsxIcon {
background-image: url(./assets/xlsx.svg);
background-image: url(~@/assets/xlsx.svg);
}
.csvIcon {
background-image: url(./assets/csv.svg);
background-image: url(~@/assets/csv.svg);
}
.statusItemDetail {
@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 @@
.actionIcon {
@apply bg-gray-500;
mask-image: url(~@/app/components/datasets/documents/assets/action.svg);
mask-image: url(~@/assets/action.svg);
}
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;
/* mask-image: ; */
}
......@@ -32,4 +32,4 @@ body .btn:hover {
.deleteActionItem:hover .deleteActionItemChild {
@apply text-red-500;
}
\ No newline at end of file
}
.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;
/* mask-image: ; */
}
.panelBorder {
border: 0.5px solid rgba(0, 0, 0, .05);
}
\ No newline at end of file
}
......@@ -15,6 +15,7 @@ const translation = {
communityIntro:
'Discuss with team members, contributors and developers on different channels.',
roadmap: 'See our roadmap',
appNamePlaceholder: 'Please enter the name of the app',
newApp: {
startToCreate: 'Let\'s start with your new app',
captionName: 'Give your app a name',
......@@ -36,6 +37,9 @@ const translation = {
appCreated: 'App created',
appCreateFailed: 'Failed to create app',
},
editApp: {
startToEdit: 'Edit App',
},
emoji: {
ok: 'OK',
cancel: 'Cancel',
......
......@@ -14,6 +14,7 @@ const translation = {
join: '参与社区',
communityIntro: '与团队成员、贡献者和开发者在不同频道中交流',
roadmap: '产品路线图',
appNamePlaceholder: '请输入应用名称',
newApp: {
startToCreate: '开始创建一个新应用',
captionName: '给应用起个名字',
......@@ -35,6 +36,9 @@ const translation = {
appCreated: '应用已创建',
appCreateFailed: '应用创建失败',
},
editApp: {
startToEdit: '编辑应用',
},
emoji: {
ok: '确认',
cancel: '取消',
......
......@@ -23,6 +23,8 @@ const translation = {
lineBreak: 'Line break',
sure: 'I\'m sure',
download: 'Download',
delete: 'Delete',
settings: 'Settings',
},
placeholder: {
input: 'Please enter',
......@@ -32,6 +34,7 @@ const translation = {
char: 'chars',
},
actionMsg: {
noModification: 'No modifications at the moment.',
modifiedSuccessfully: 'Modified successfully',
modificationFailed: 'Modification failed',
copySuccessfully: 'Copied successfully',
......
......@@ -23,6 +23,8 @@ const translation = {
lineBreak: '换行',
sure: '我确定',
download: '下载',
delete: '删除',
settings: '设置',
},
placeholder: {
input: '请输入',
......@@ -32,6 +34,7 @@ const translation = {
char: '个字符',
},
actionMsg: {
noModification: '暂无修改',
modifiedSuccessfully: '修改成功',
modificationFailed: '修改失败',
copySuccessfully: '复制成功',
......
......@@ -77,6 +77,8 @@ export type CreateAppResponse = App
export type UpdateAppNameResponse = App
export type UpdateAppIconResponse = App
export type UpdateAppSiteCodeResponse = { app_id: string } & SiteConfig
export type AppDailyConversationsResponse = {
......
......@@ -136,6 +136,7 @@ export type DataSourceInfo = {
created_by: string
extension: string
}
notion_page_icon?: string
}
export type InitialDocumentDetail = {
......
import type { Fetcher } from 'swr'
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 { AppMode, ModelConfig } from '@/types/app'
......@@ -24,8 +24,13 @@ export const deleteApp: Fetcher<CommonResponse, string> = (appID) => {
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
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>
}
......
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