feat: add model provider logo upload (#4408)
* feat: add model provider logo upload * Update index.tsx * fix: upload image delete
This commit is contained in:
parent
5c44f71684
commit
d5fcef39d3
@ -7,7 +7,7 @@ import { setAvatar } from '@renderer/store/runtime'
|
|||||||
import { setUserName } from '@renderer/store/settings'
|
import { setUserName } from '@renderer/store/settings'
|
||||||
import { compressImage, isEmoji } from '@renderer/utils'
|
import { compressImage, isEmoji } from '@renderer/utils'
|
||||||
import { Avatar, Dropdown, Input, Modal, Popover, Upload } from 'antd'
|
import { Avatar, Dropdown, Input, Modal, Popover, Upload } from 'antd'
|
||||||
import { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,53 @@
|
|||||||
|
import { Center, VStack } from '@renderer/components/Layout'
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
|
import ImageStorage from '@renderer/services/ImageStorage'
|
||||||
import { Provider, ProviderType } from '@renderer/types'
|
import { Provider, ProviderType } from '@renderer/types'
|
||||||
import { Divider, Form, Input, Modal, Select } from 'antd'
|
import { compressImage } from '@renderer/utils'
|
||||||
import { useState } from 'react'
|
import { Divider, Dropdown, Form, Input, Modal, Select, Upload } from 'antd'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
provider?: Provider
|
provider?: Provider
|
||||||
resolve: (result: { name: string; type: ProviderType }) => void
|
resolve: (result: { name: string; type: ProviderType; logo?: string; logoFile?: File }) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const [name, setName] = useState(provider?.name || '')
|
const [name, setName] = useState(provider?.name || '')
|
||||||
const [type, setType] = useState<ProviderType>(provider?.type || 'openai')
|
const [type, setType] = useState<ProviderType>(provider?.type || 'openai')
|
||||||
|
const [logo, setLogo] = useState<string | null>(null)
|
||||||
|
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onOk = () => {
|
useEffect(() => {
|
||||||
|
if (provider?.id) {
|
||||||
|
const loadLogo = async () => {
|
||||||
|
try {
|
||||||
|
const logoData = await ImageStorage.get(`provider-${provider.id}`)
|
||||||
|
if (logoData) {
|
||||||
|
setLogo(logoData)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load logo', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadLogo()
|
||||||
|
}
|
||||||
|
}, [provider])
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
resolve({ name, type })
|
|
||||||
|
// 返回结果,但不包含文件对象,因为文件已经直接保存到 ImageStorage
|
||||||
|
const result = {
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
logo: logo || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
@ -26,11 +56,94 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
resolve({ name, type })
|
resolve({ name, type, logo: logo || undefined })
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonDisabled = name.length === 0
|
const buttonDisabled = name.length === 0
|
||||||
|
|
||||||
|
const handleReset = async () => {
|
||||||
|
try {
|
||||||
|
setLogo(null)
|
||||||
|
|
||||||
|
if (provider?.id) {
|
||||||
|
await ImageStorage.set(`provider-${provider.id}`, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
setDropdownOpen(false)
|
||||||
|
} catch (error: any) {
|
||||||
|
window.message.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInitials = () => {
|
||||||
|
return name.charAt(0).toUpperCase() || 'P'
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
key: 'upload',
|
||||||
|
label: (
|
||||||
|
<div style={{ width: '100%', textAlign: 'center' }}>
|
||||||
|
<Upload
|
||||||
|
customRequest={() => {}}
|
||||||
|
accept="image/png, image/jpeg, image/gif"
|
||||||
|
itemRender={() => null}
|
||||||
|
maxCount={1}
|
||||||
|
onChange={async ({ file }) => {
|
||||||
|
try {
|
||||||
|
const _file = file.originFileObj as File
|
||||||
|
let logoData: string | Blob
|
||||||
|
|
||||||
|
if (_file.type === 'image/gif') {
|
||||||
|
logoData = _file
|
||||||
|
} else {
|
||||||
|
logoData = await compressImage(_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider?.id) {
|
||||||
|
if (logoData instanceof Blob && !(logoData instanceof File)) {
|
||||||
|
const fileFromBlob = new File([logoData], 'logo.png', { type: logoData.type })
|
||||||
|
await ImageStorage.set(`provider-${provider.id}`, fileFromBlob)
|
||||||
|
} else {
|
||||||
|
await ImageStorage.set(`provider-${provider.id}`, logoData)
|
||||||
|
}
|
||||||
|
const savedLogo = await ImageStorage.get(`provider-${provider.id}`)
|
||||||
|
setLogo(savedLogo)
|
||||||
|
} else {
|
||||||
|
// 临时保存在内存中,等创建 provider 后会在调用方保存
|
||||||
|
const tempUrl = await new Promise<string>((resolve) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = () => resolve(reader.result as string)
|
||||||
|
reader.readAsDataURL(logoData)
|
||||||
|
})
|
||||||
|
setLogo(tempUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
setDropdownOpen(false)
|
||||||
|
} catch (error: any) {
|
||||||
|
window.message.error(error.message)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{t('settings.general.image_upload')}
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'reset',
|
||||||
|
label: (
|
||||||
|
<div
|
||||||
|
style={{ width: '100%', textAlign: 'center' }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleReset()
|
||||||
|
}}>
|
||||||
|
{t('settings.general.avatar.reset')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
@ -43,6 +156,23 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
title={t('settings.provider.add.title')}
|
title={t('settings.provider.add.title')}
|
||||||
okButtonProps={{ disabled: buttonDisabled }}>
|
okButtonProps={{ disabled: buttonDisabled }}>
|
||||||
<Divider style={{ margin: '8px 0' }} />
|
<Divider style={{ margin: '8px 0' }} />
|
||||||
|
|
||||||
|
<Center mt="10px" mb="20px">
|
||||||
|
<VStack alignItems="center" gap="10px">
|
||||||
|
<Dropdown
|
||||||
|
menu={{ items }}
|
||||||
|
trigger={['click']}
|
||||||
|
open={dropdownOpen}
|
||||||
|
align={{ offset: [0, 4] }}
|
||||||
|
placement="bottom"
|
||||||
|
onOpenChange={(visible) => {
|
||||||
|
setDropdownOpen(visible)
|
||||||
|
}}>
|
||||||
|
{logo ? <ProviderLogo src={logo} /> : <ProviderInitialsLogo>{getInitials()}</ProviderInitialsLogo>}
|
||||||
|
</Dropdown>
|
||||||
|
</VStack>
|
||||||
|
</Center>
|
||||||
|
|
||||||
<Form layout="vertical" style={{ gap: 8 }}>
|
<Form layout="vertical" style={{ gap: 8 }}>
|
||||||
<Form.Item label={t('settings.provider.add.name')} style={{ marginBottom: 8 }}>
|
<Form.Item label={t('settings.provider.add.name')} style={{ marginBottom: 8 }}>
|
||||||
<Input
|
<Input
|
||||||
@ -70,13 +200,46 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ProviderLogo = styled.img`
|
||||||
|
cursor: pointer;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 12px;
|
||||||
|
object-fit: contain;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
padding: 5px;
|
||||||
|
border: 0.5px solid var(--color-border);
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderInitialsLogo = styled.div`
|
||||||
|
cursor: pointer;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
border: 0.5px solid var(--color-border);
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export default class AddProviderPopup {
|
export default class AddProviderPopup {
|
||||||
static topviewId = 0
|
static topviewId = 0
|
||||||
static hide() {
|
static hide() {
|
||||||
TopView.hide('AddProviderPopup')
|
TopView.hide('AddProviderPopup')
|
||||||
}
|
}
|
||||||
static show(provider?: Provider) {
|
static show(provider?: Provider) {
|
||||||
return new Promise<{ name: string; type: ProviderType }>((resolve) => {
|
return new Promise<{ name: string; type: ProviderType; logo?: string; logoFile?: File }>((resolve) => {
|
||||||
TopView.show(
|
TopView.show(
|
||||||
<PopupContainer
|
<PopupContainer
|
||||||
provider={provider}
|
provider={provider}
|
||||||
|
|||||||
@ -3,10 +3,11 @@ import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea
|
|||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
import { getProviderLogo } from '@renderer/config/providers'
|
||||||
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import ImageStorage from '@renderer/services/ImageStorage'
|
||||||
import { Provider } from '@renderer/types'
|
import { Provider } from '@renderer/types'
|
||||||
import { droppableReorder, generateColorFromChar, getFirstCharacter, uuid } from '@renderer/utils'
|
import { droppableReorder, generateColorFromChar, getFirstCharacter, uuid } from '@renderer/utils'
|
||||||
import { Avatar, Button, Dropdown, Input, MenuProps, Tag } from 'antd'
|
import { Avatar, Button, Dropdown, Input, MenuProps, Tag } from 'antd'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -20,6 +21,28 @@ const ProvidersList: FC = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [searchText, setSearchText] = useState<string>('')
|
const [searchText, setSearchText] = useState<string>('')
|
||||||
const [dragging, setDragging] = useState(false)
|
const [dragging, setDragging] = useState(false)
|
||||||
|
const [providerLogos, setProviderLogos] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadAllLogos = async () => {
|
||||||
|
const logos: Record<string, string> = {}
|
||||||
|
for (const provider of providers) {
|
||||||
|
if (provider.id) {
|
||||||
|
try {
|
||||||
|
const logoData = await ImageStorage.get(`provider-${provider.id}`)
|
||||||
|
if (logoData) {
|
||||||
|
logos[provider.id] = logoData
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load logo for provider ${provider.id}`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setProviderLogos(logos)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAllLogos()
|
||||||
|
}, [providers])
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
const onDragEnd = (result: DropResult) => {
|
||||||
setDragging(false)
|
setDragging(false)
|
||||||
@ -32,15 +55,15 @@ const ProvidersList: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onAddProvider = async () => {
|
const onAddProvider = async () => {
|
||||||
const { name: prividerName, type } = await AddProviderPopup.show()
|
const { name: providerName, type, logo } = await AddProviderPopup.show()
|
||||||
|
|
||||||
if (!prividerName.trim()) {
|
if (!providerName.trim()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name: prividerName.trim(),
|
name: providerName.trim(),
|
||||||
type,
|
type,
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: '',
|
apiHost: '',
|
||||||
@ -49,6 +72,21 @@ const ProvidersList: FC = () => {
|
|||||||
isSystem: false
|
isSystem: false
|
||||||
} as Provider
|
} as Provider
|
||||||
|
|
||||||
|
let updatedLogos = { ...providerLogos }
|
||||||
|
if (logo) {
|
||||||
|
try {
|
||||||
|
await ImageStorage.set(`provider-${provider.id}`, logo)
|
||||||
|
updatedLogos = {
|
||||||
|
...updatedLogos,
|
||||||
|
[provider.id]: logo
|
||||||
|
}
|
||||||
|
setProviderLogos(updatedLogos)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save logo', error)
|
||||||
|
window.message.error('保存Provider Logo失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addProvider(provider)
|
addProvider(provider)
|
||||||
setSelectedProvider(provider)
|
setSelectedProvider(provider)
|
||||||
}
|
}
|
||||||
@ -60,8 +98,36 @@ const ProvidersList: FC = () => {
|
|||||||
key: 'edit',
|
key: 'edit',
|
||||||
icon: <EditOutlined />,
|
icon: <EditOutlined />,
|
||||||
async onClick() {
|
async onClick() {
|
||||||
const { name, type } = await AddProviderPopup.show(provider)
|
const { name, type, logoFile, logo } = await AddProviderPopup.show(provider)
|
||||||
name && updateProvider({ ...provider, name, type })
|
|
||||||
|
if (name) {
|
||||||
|
updateProvider({ ...provider, name, type })
|
||||||
|
if (provider.id) {
|
||||||
|
if (logoFile && logo) {
|
||||||
|
try {
|
||||||
|
await ImageStorage.set(`provider-${provider.id}`, logo)
|
||||||
|
setProviderLogos((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[provider.id]: logo
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save logo', error)
|
||||||
|
window.message.error('更新Provider Logo失败')
|
||||||
|
}
|
||||||
|
} else if (logo === undefined && logoFile === undefined) {
|
||||||
|
try {
|
||||||
|
await ImageStorage.set(`provider-${provider.id}`, '')
|
||||||
|
setProviderLogos((prev) => {
|
||||||
|
const newLogos = { ...prev }
|
||||||
|
delete newLogos[provider.id]
|
||||||
|
return newLogos
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to reset logo', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -76,7 +142,21 @@ const ProvidersList: FC = () => {
|
|||||||
okButtonProps: { danger: true },
|
okButtonProps: { danger: true },
|
||||||
okText: t('common.delete'),
|
okText: t('common.delete'),
|
||||||
centered: true,
|
centered: true,
|
||||||
onOk: () => {
|
onOk: async () => {
|
||||||
|
// 删除provider前先清理其logo
|
||||||
|
if (provider.id) {
|
||||||
|
try {
|
||||||
|
await ImageStorage.remove(`provider-${provider.id}`)
|
||||||
|
setProviderLogos((prev) => {
|
||||||
|
const newLogos = { ...prev }
|
||||||
|
delete newLogos[provider.id]
|
||||||
|
return newLogos
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete logo', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setSelectedProvider(providers.filter((p) => p.isSystem)[0])
|
setSelectedProvider(providers.filter((p) => p.isSystem)[0])
|
||||||
removeProvider(provider)
|
removeProvider(provider)
|
||||||
}
|
}
|
||||||
@ -96,17 +176,33 @@ const ProvidersList: FC = () => {
|
|||||||
return menus
|
return menus
|
||||||
}
|
}
|
||||||
|
|
||||||
//will match the providers and the models that provider provides
|
const getProviderAvatar = (provider: Provider) => {
|
||||||
|
if (provider.isSystem) {
|
||||||
|
return <ProviderLogo shape="square" src={getProviderLogo(provider.id)} size={25} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const customLogo = providerLogos[provider.id]
|
||||||
|
if (customLogo) {
|
||||||
|
return <ProviderLogo shape="square" src={customLogo} size={25} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProviderLogo
|
||||||
|
size={25}
|
||||||
|
shape="square"
|
||||||
|
style={{ backgroundColor: generateColorFromChar(provider.name), minWidth: 25 }}>
|
||||||
|
{getFirstCharacter(provider.name)}
|
||||||
|
</ProviderLogo>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const filteredProviders = providers.filter((provider) => {
|
const filteredProviders = providers.filter((provider) => {
|
||||||
// 获取 provider 的名称
|
|
||||||
const providerName = provider.isSystem ? t(`provider.${provider.id}`) : provider.name
|
const providerName = provider.isSystem ? t(`provider.${provider.id}`) : provider.name
|
||||||
|
|
||||||
// 检查 provider 的 id 和 name 是否匹配搜索条件
|
|
||||||
const isProviderMatch =
|
const isProviderMatch =
|
||||||
provider.id.toLowerCase().includes(searchText.toLowerCase()) ||
|
provider.id.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
providerName.toLowerCase().includes(searchText.toLowerCase())
|
providerName.toLowerCase().includes(searchText.toLowerCase())
|
||||||
|
|
||||||
// 检查 provider.models 中是否有 model 的 id 或 name 匹配搜索条件
|
|
||||||
const isModelMatch = provider.models.some((model) => {
|
const isModelMatch = provider.models.some((model) => {
|
||||||
return (
|
return (
|
||||||
model.id.toLowerCase().includes(searchText.toLowerCase()) ||
|
model.id.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
@ -114,7 +210,6 @@ const ProvidersList: FC = () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 如果 provider 或 model 匹配,则保留该 provider
|
|
||||||
return isProviderMatch || isModelMatch
|
return isProviderMatch || isModelMatch
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -161,17 +256,7 @@ const ProvidersList: FC = () => {
|
|||||||
key={JSON.stringify(provider)}
|
key={JSON.stringify(provider)}
|
||||||
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
||||||
onClick={() => setSelectedProvider(provider)}>
|
onClick={() => setSelectedProvider(provider)}>
|
||||||
{provider.isSystem && (
|
{getProviderAvatar(provider)}
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.id)} size={25} />
|
|
||||||
)}
|
|
||||||
{!provider.isSystem && (
|
|
||||||
<ProviderLogo
|
|
||||||
size={25}
|
|
||||||
shape="square"
|
|
||||||
style={{ backgroundColor: generateColorFromChar(provider.name), minWidth: 25 }}>
|
|
||||||
{getFirstCharacter(provider.name)}
|
|
||||||
</ProviderLogo>
|
|
||||||
)}
|
|
||||||
<ProviderItemName className="text-nowrap">
|
<ProviderItemName className="text-nowrap">
|
||||||
{provider.isSystem ? t(`provider.${provider.id}`) : provider.name}
|
{provider.isSystem ? t(`provider.${provider.id}`) : provider.name}
|
||||||
</ProviderItemName>
|
</ProviderItemName>
|
||||||
|
|||||||
@ -34,4 +34,17 @@ export default class ImageStorage {
|
|||||||
const id = IMAGE_PREFIX + key
|
const id = IMAGE_PREFIX + key
|
||||||
return (await db.settings.get(id))?.value
|
return (await db.settings.get(id))?.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async remove(key: string): Promise<void> {
|
||||||
|
const id = IMAGE_PREFIX + key
|
||||||
|
try {
|
||||||
|
const record = await db.settings.get(id)
|
||||||
|
if (record) {
|
||||||
|
await db.settings.delete(id)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error removing the image', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user