mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-03 11:19:10 +08:00
fix: complete PoeLogo rendering support across provider UI components (#9756)
* fix: PoeLogo in ProviderLogoPicker * fix: PoeLogo in AddProviderPopup 该文件缺少足够参数匹配‘poe’,鉴于目前只有poe用的‘svg’,简便起见所以只匹配‘svg’而不匹配‘poe’,以后可能需要重构代码 * fix: PoeLogo in ProviderList * refactor: provider logo rendering logic 1.三个地方渲染头像统一命名成getProviderLogo 2.鉴于目前PROVIDER_LOGO_MAP中只有poe的是‘svg’,故对于判断简化处理,以后有其它提供商改成‘svg’时再重构代码 3.优化内置头像选择器组件部分代码 * Update index.tsx * refactor(provider-logo): 将获取provider logo的逻辑统一到useProviderLogo钩子中 将原本分散在各处的获取provider logo的逻辑集中到新创建的useProviderLogo钩子中 移除config/providers.ts中的getProviderLogo函数 修改所有使用getProviderLogo的地方改为使用新钩子 * refactor(useProvider): 移除未使用的getProviderLogo函数 * refactor(ProviderAvatar): 重构提供者头像组件为更灵活的API设计 将原有的getProviderAvatar函数重构为ProviderAvatar组件,提供更灵活的属性和样式控制 统一所有使用提供者头像的地方调用新组件,移除重复的样式代码 * refactor(ImageStorage): 使用put替代add方法更新数据库 将db.settings.add方法替换为db.settings.put,以提高数据更新的准确性和一致性 * feat(llm): 添加logos状态管理及相关操作 添加logos状态字段及setLogos、setLogo、removeLogo操作方法,用于管理不同LLM提供商的logo * refactor(hooks): 使用 useCallback 优化 useTimer 的性能 将 setTimeoutTimer 和 setIntervalTimer 函数用 useCallback 包裹以避免不必要的重新创建 * refactor(provider-logo): 重构提供商logo管理逻辑,使用redux存储logo状态 - 将logo管理逻辑从组件中抽离到useProviderLogo hook - 使用redux存储和更新logo状态 - 优化logo的加载和保存流程 - 统一处理系统提供商和自定义提供商的logo显示 - 移除冗余的ImageStorage直接调用 * test(api): 修复ApiService测试中的mock数据缺失 * fix(store): 更新持久化存储版本至144并添加迁移逻辑 添加版本144的迁移逻辑,初始化llm.logos状态以修复潜在问题 --------- Co-authored-by: icarus <eurfelux@gmail.com>
This commit is contained in:
parent
86d8e10dcb
commit
df7fd26907
@ -1,6 +1,9 @@
|
|||||||
import { SearchOutlined } from '@ant-design/icons'
|
import { SearchOutlined } from '@ant-design/icons'
|
||||||
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
||||||
|
import { useProviderAvatar } from '@renderer/hooks/useProviderLogo'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
|
import { useAppSelector } from '@renderer/store'
|
||||||
|
import { isSystemProvider } from '@renderer/types'
|
||||||
import { Input, Tooltip } from 'antd'
|
import { Input, Tooltip } from 'antd'
|
||||||
import { FC, useMemo, useState } from 'react'
|
import { FC, useMemo, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -12,19 +15,21 @@ interface Props {
|
|||||||
// 用于选择内置头像的提供商Logo选择器组件
|
// 用于选择内置头像的提供商Logo选择器组件
|
||||||
const ProviderLogoPicker: FC<Props> = ({ onProviderClick }) => {
|
const ProviderLogoPicker: FC<Props> = ({ onProviderClick }) => {
|
||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
|
const providers = useAppSelector((state) => state.llm.providers)
|
||||||
|
|
||||||
|
const { ProviderAvatar } = useProviderAvatar()
|
||||||
const filteredProviders = useMemo(() => {
|
const filteredProviders = useMemo(() => {
|
||||||
const providers = Object.entries(PROVIDER_LOGO_MAP).map(([id, logo]) => ({
|
const _providers = providers.filter(isSystemProvider).map((p) => ({
|
||||||
id,
|
id: p.id,
|
||||||
logo,
|
name: p.name,
|
||||||
name: getProviderLabel(id)
|
label: getProviderLabel(p.id),
|
||||||
|
logo: PROVIDER_LOGO_MAP[p.id]
|
||||||
}))
|
}))
|
||||||
|
if (!searchText) return _providers
|
||||||
if (!searchText) return providers
|
|
||||||
|
|
||||||
const searchLower = searchText.toLowerCase()
|
const searchLower = searchText.toLowerCase()
|
||||||
return providers.filter((p) => p.name.toLowerCase().includes(searchLower))
|
return _providers.filter((p) => `${p.name} ${p.id} ${p.label}`.toLowerCase().includes(searchLower))
|
||||||
}, [searchText])
|
}, [providers, searchText])
|
||||||
|
|
||||||
const handleProviderClick = (event: React.MouseEvent, providerId: string) => {
|
const handleProviderClick = (event: React.MouseEvent, providerId: string) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
@ -48,10 +53,10 @@ const ProviderLogoPicker: FC<Props> = ({ onProviderClick }) => {
|
|||||||
/>
|
/>
|
||||||
</SearchContainer>
|
</SearchContainer>
|
||||||
<LogoGrid>
|
<LogoGrid>
|
||||||
{filteredProviders.map(({ id, logo, name }) => (
|
{filteredProviders.map(({ id, label }) => (
|
||||||
<Tooltip key={id} title={name} placement="top" mouseLeaveDelay={0}>
|
<Tooltip key={id} title={label} placement="top" mouseLeaveDelay={0}>
|
||||||
<LogoItem onClick={(e) => handleProviderClick(e, id)}>
|
<LogoItem onClick={(e) => handleProviderClick(e, id)}>
|
||||||
<img src={logo} alt={name} draggable={false} />
|
<ProviderAvatar pid={id} size={32} />
|
||||||
</LogoItem>
|
</LogoItem>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -664,10 +664,6 @@ export const PROVIDER_LOGO_MAP: AtLeast<SystemProviderId, string> = {
|
|||||||
poe: 'svg' // use svg icon component
|
poe: 'svg' // use svg icon component
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function getProviderLogo(providerId: string) {
|
|
||||||
return PROVIDER_LOGO_MAP[providerId as keyof typeof PROVIDER_LOGO_MAP]
|
|
||||||
}
|
|
||||||
|
|
||||||
// export const SUPPORTED_REANK_PROVIDERS = ['silicon', 'jina', 'voyageai', 'dashscope', 'aihubmix']
|
// export const SUPPORTED_REANK_PROVIDERS = ['silicon', 'jina', 'voyageai', 'dashscope', 'aihubmix']
|
||||||
export const NOT_SUPPORTED_RERANK_PROVIDERS = ['ollama', 'lmstudio'] as const satisfies SystemProviderId[]
|
export const NOT_SUPPORTED_RERANK_PROVIDERS = ['ollama', 'lmstudio'] as const satisfies SystemProviderId[]
|
||||||
export const ONLY_SUPPORTED_DIMENSION_PROVIDERS = ['ollama', 'infini'] as const satisfies SystemProviderId[]
|
export const ONLY_SUPPORTED_DIMENSION_PROVIDERS = ['ollama', 'infini'] as const satisfies SystemProviderId[]
|
||||||
|
|||||||
112
src/renderer/src/hooks/useProviderLogo.tsx
Normal file
112
src/renderer/src/hooks/useProviderLogo.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { PoeLogo } from '@renderer/components/Icons'
|
||||||
|
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
||||||
|
import ImageStorage from '@renderer/services/ImageStorage'
|
||||||
|
import { getProviderById } from '@renderer/services/ProviderService'
|
||||||
|
import { useAppSelector } from '@renderer/store'
|
||||||
|
import { removeLogo, setLogo, setLogos } from '@renderer/store/llm'
|
||||||
|
import { isSystemProviderId, Provider } from '@renderer/types'
|
||||||
|
import { generateColorFromChar, getFirstCharacter, getForegroundColor } from '@renderer/utils'
|
||||||
|
import { Avatar, AvatarProps } from 'antd'
|
||||||
|
import { AvatarSize } from 'antd/es/avatar/AvatarContext'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import { useCallback, useEffect } from 'react'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import styled, { CSSProperties } from 'styled-components'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('useProviderLogo')
|
||||||
|
|
||||||
|
export const useProviderAvatar = () => {
|
||||||
|
const providers = useAppSelector((state) => state.llm.providers)
|
||||||
|
const logos = useAppSelector((state) => state.llm.logos)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const saveLogo = useCallback(
|
||||||
|
async (logo: string, providerId: string) => {
|
||||||
|
ImageStorage.set(`provider-${providerId}`, logo)
|
||||||
|
dispatch(setLogo({ id: providerId, logo }))
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
|
||||||
|
const deleteLogo = useCallback(
|
||||||
|
async (id: string) => {
|
||||||
|
ImageStorage.remove(id).catch((e) => logger.error('Falied to remove image.', e as Error))
|
||||||
|
dispatch(removeLogo(id))
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getLogos = async () => {
|
||||||
|
const _logos = {}
|
||||||
|
for (const p of providers) {
|
||||||
|
_logos[p.id] = await ImageStorage.get(`provider-${p.id}`)
|
||||||
|
}
|
||||||
|
dispatch(setLogos(_logos))
|
||||||
|
}
|
||||||
|
getLogos()
|
||||||
|
}, [dispatch, providers])
|
||||||
|
|
||||||
|
const ProviderAvatar = useCallback(
|
||||||
|
({
|
||||||
|
pid,
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
src,
|
||||||
|
style,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
pid?: string
|
||||||
|
name?: string
|
||||||
|
size?: number
|
||||||
|
src?: string
|
||||||
|
} & AvatarProps) => {
|
||||||
|
if (src) {
|
||||||
|
return <ProviderLogo draggable="false" shape="square" src={src} size={size} style={style} {...rest} />
|
||||||
|
}
|
||||||
|
let provider: Provider | undefined
|
||||||
|
if (pid) {
|
||||||
|
// 特殊处理一下svg格式
|
||||||
|
if (isSystemProviderId(pid)) {
|
||||||
|
const logoSrc = PROVIDER_LOGO_MAP[pid]
|
||||||
|
switch (pid) {
|
||||||
|
case 'poe':
|
||||||
|
return <PoeLogo fontSize={typeof size === 'number' ? size : 18} style={style} />
|
||||||
|
default:
|
||||||
|
return <ProviderLogo draggable="false" shape="square" src={logoSrc} size={size} style={style} {...rest} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const customLogo = logos[pid]
|
||||||
|
if (customLogo) {
|
||||||
|
return <ProviderLogo draggable="false" shape="square" src={customLogo} size={size} style={style} {...rest} />
|
||||||
|
}
|
||||||
|
if (!name) {
|
||||||
|
// generate a avatar for custom provider
|
||||||
|
provider = getProviderById(pid)
|
||||||
|
if (!provider) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <GeneratedAvatar name={name ?? provider?.name ?? 'P'} size={size} style={style} />
|
||||||
|
},
|
||||||
|
[logos]
|
||||||
|
)
|
||||||
|
|
||||||
|
function GeneratedAvatar({ name, size, style }: { name: string; size?: AvatarSize; style?: CSSProperties }) {
|
||||||
|
const backgroundColor = generateColorFromChar(name)
|
||||||
|
const color = name ? getForegroundColor(backgroundColor) : 'white'
|
||||||
|
return (
|
||||||
|
<ProviderLogo size={size} shape="square" style={{ backgroundColor, color, ...style }}>
|
||||||
|
{getFirstCharacter(!isEmpty(name) ? name : 'P')}
|
||||||
|
</ProviderLogo>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return { ProviderAvatar, GeneratedAvatar, saveLogo, deleteLogo, logos }
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProviderLogo = styled(Avatar)`
|
||||||
|
border: 0.5px solid var(--color-border);
|
||||||
|
`
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useCallback, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定时器管理 Hook,用于管理 setTimeout 和 setInterval 定时器,支持通过 key 来标识不同的定时器
|
* 定时器管理 Hook,用于管理 setTimeout 和 setInterval 定时器,支持通过 key 来标识不同的定时器
|
||||||
@ -65,12 +65,12 @@ export const useTimer = () => {
|
|||||||
* cleanup();
|
* cleanup();
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
const setTimeoutTimer = (key: string, ...args: Parameters<typeof setTimeout>) => {
|
const setTimeoutTimer = useCallback((key: string, ...args: Parameters<typeof setTimeout>) => {
|
||||||
clearTimeout(timeoutMapRef.current.get(key))
|
clearTimeout(timeoutMapRef.current.get(key))
|
||||||
const timer = setTimeout(...args)
|
const timer = setTimeout(...args)
|
||||||
timeoutMapRef.current.set(key, timer)
|
timeoutMapRef.current.set(key, timer)
|
||||||
return () => clearTimeoutTimer(key)
|
return () => clearTimeoutTimer(key)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置一个 setInterval 定时器
|
* 设置一个 setInterval 定时器
|
||||||
@ -89,12 +89,12 @@ export const useTimer = () => {
|
|||||||
* cleanup();
|
* cleanup();
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
const setIntervalTimer = (key: string, ...args: Parameters<typeof setInterval>) => {
|
const setIntervalTimer = useCallback((key: string, ...args: Parameters<typeof setInterval>) => {
|
||||||
clearInterval(intervalMapRef.current.get(key))
|
clearInterval(intervalMapRef.current.get(key))
|
||||||
const timer = setInterval(...args)
|
const timer = setInterval(...args)
|
||||||
intervalMapRef.current.set(key, timer)
|
intervalMapRef.current.set(key, timer)
|
||||||
return () => clearIntervalTimer(key)
|
return () => clearIntervalTimer(key)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除指定 key 的 setTimeout 定时器
|
* 清除指定 key 的 setTimeout 定时器
|
||||||
|
|||||||
@ -7,11 +7,11 @@ import { HStack } from '@renderer/components/Layout'
|
|||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import TranslateButton from '@renderer/components/TranslateButton'
|
import TranslateButton from '@renderer/components/TranslateButton'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
|
||||||
import { LanguagesEnum } from '@renderer/config/translate'
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import { useProviderAvatar } from '@renderer/hooks/useProviderLogo'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
@ -22,7 +22,7 @@ import { setGenerating } from '@renderer/store/runtime'
|
|||||||
import type { FileMetadata } from '@renderer/types'
|
import type { FileMetadata } from '@renderer/types'
|
||||||
import type { PaintingAction, PaintingsState } from '@renderer/types'
|
import type { PaintingAction, PaintingsState } from '@renderer/types'
|
||||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||||
import { Avatar, Button, Input, InputNumber, Radio, Segmented, Select, Slider, Switch, Tooltip, Upload } from 'antd'
|
import { Button, Input, InputNumber, Radio, Segmented, Select, Slider, Switch, Tooltip, Upload } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { Info } from 'lucide-react'
|
import { Info } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
@ -54,6 +54,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
aihubmix_image_edit,
|
aihubmix_image_edit,
|
||||||
aihubmix_image_upscale
|
aihubmix_image_upscale
|
||||||
} = usePaintings()
|
} = usePaintings()
|
||||||
|
const { ProviderAvatar } = useProviderAvatar()
|
||||||
|
|
||||||
const paintings = useMemo(() => {
|
const paintings = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -847,12 +848,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
||||||
<SettingHelpLink target="_blank" href={aihubmixProvider.apiHost}>
|
<SettingHelpLink target="_blank" href={aihubmixProvider.apiHost}>
|
||||||
{t('paintings.learn_more')}
|
{t('paintings.learn_more')}
|
||||||
<ProviderLogo
|
<ProviderAvatar pid={aihubmixProvider.id} size={16} style={{ marginLeft: 5 }} />
|
||||||
shape="square"
|
|
||||||
src={getProviderLogo(aihubmixProvider.id)}
|
|
||||||
size={16}
|
|
||||||
style={{ marginLeft: 5 }}
|
|
||||||
/>
|
|
||||||
</SettingHelpLink>
|
</SettingHelpLink>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
|
|
||||||
@ -860,7 +856,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
{providerOptions.map((provider) => (
|
{providerOptions.map((provider) => (
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
<Select.Option value={provider.value} key={provider.value}>
|
||||||
<SelectOptionContainer>
|
<SelectOptionContainer>
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
<ProviderAvatar pid={provider.value ?? ''} size={16} />
|
||||||
{provider.label}
|
{provider.label}
|
||||||
</SelectOptionContainer>
|
</SelectOptionContainer>
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
@ -1029,10 +1025,6 @@ const StyledInputNumber = styled(InputNumber)`
|
|||||||
width: 70px;
|
width: 70px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
|
||||||
border: 0.5px solid var(--color-border);
|
|
||||||
`
|
|
||||||
|
|
||||||
// 添加新的样式组件
|
// 添加新的样式组件
|
||||||
const ModeSegmentedContainer = styled.div`
|
const ModeSegmentedContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navb
|
|||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
|
||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import { useProviderAvatar } from '@renderer/hooks/useProviderLogo'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
@ -15,7 +15,7 @@ import { setGenerating } from '@renderer/store/runtime'
|
|||||||
import type { FileMetadata } from '@renderer/types'
|
import type { FileMetadata } from '@renderer/types'
|
||||||
import { convertToBase64, uuid } from '@renderer/utils'
|
import { convertToBase64, uuid } from '@renderer/utils'
|
||||||
import { DmxapiPainting } from '@types'
|
import { DmxapiPainting } from '@types'
|
||||||
import { Avatar, Button, Input, InputNumber, Segmented, Select, Switch, Tooltip } from 'antd'
|
import { Button, Input, InputNumber, Segmented, Select, Switch, Tooltip } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { Info } from 'lucide-react'
|
import { Info } from 'lucide-react'
|
||||||
import React, { FC, useEffect, useRef, useState } from 'react'
|
import React, { FC, useEffect, useRef, useState } from 'react'
|
||||||
@ -46,6 +46,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const [painting, setPainting] = useState<DmxapiPainting>(dmxapi_paintings?.[0] || DEFAULT_PAINTING)
|
const [painting, setPainting] = useState<DmxapiPainting>(dmxapi_paintings?.[0] || DEFAULT_PAINTING)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const providers = useAllProviders()
|
const providers = useAllProviders()
|
||||||
|
const { ProviderAvatar } = useProviderAvatar()
|
||||||
const providerOptions = Options.map((option) => {
|
const providerOptions = Options.map((option) => {
|
||||||
const provider = providers.find((p) => p.id === option)
|
const provider = providers.find((p) => p.id === option)
|
||||||
if (provider) {
|
if (provider) {
|
||||||
@ -814,19 +815,15 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
<SettingHelpLink target="_blank" href={TOP_UP_URL}>
|
<SettingHelpLink target="_blank" href={TOP_UP_URL}>
|
||||||
{t('paintings.top_up')}
|
{t('paintings.top_up')}
|
||||||
</SettingHelpLink>
|
</SettingHelpLink>
|
||||||
<ProviderLogo
|
<ProviderAvatar pid={dmxapiProvider.id} size={16} style={{ marginLeft: 5 }} />
|
||||||
shape="square"
|
|
||||||
src={getProviderLogo(dmxapiProvider.id)}
|
|
||||||
size={16}
|
|
||||||
style={{ marginLeft: 5 }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
<Select value={providerOptions[3].value} onChange={handleProviderChange} style={{ marginBottom: 15 }}>
|
<Select value={providerOptions[3].value} onChange={handleProviderChange} style={{ marginBottom: 15 }}>
|
||||||
{providerOptions.map((provider) => (
|
{providerOptions.map((provider) => (
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
<Select.Option value={provider.value} key={provider.value}>
|
||||||
<SelectOptionContainer>
|
<SelectOptionContainer>
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
<ProviderAvatar pid={provider.value || ''} size={16} />
|
||||||
|
|
||||||
{provider.label}
|
{provider.label}
|
||||||
</SelectOptionContainer>
|
</SelectOptionContainer>
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
@ -1038,9 +1035,6 @@ const ProviderTitleContainer = styled.div`
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
|
||||||
border: 0.5px solid var(--color-border);
|
|
||||||
`
|
|
||||||
const SelectOptionContainer = styled.div`
|
const SelectOptionContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navb
|
|||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import TranslateButton from '@renderer/components/TranslateButton'
|
import TranslateButton from '@renderer/components/TranslateButton'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
|
||||||
import { LanguagesEnum } from '@renderer/config/translate'
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import { useProviderAvatar } from '@renderer/hooks/useProviderLogo'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import {
|
import {
|
||||||
@ -29,7 +29,7 @@ import { setGenerating } from '@renderer/store/runtime'
|
|||||||
import type { PaintingAction, PaintingsState } from '@renderer/types'
|
import type { PaintingAction, PaintingsState } from '@renderer/types'
|
||||||
import { FileMetadata } from '@renderer/types'
|
import { FileMetadata } from '@renderer/types'
|
||||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||||
import { Avatar, Button, Empty, InputNumber, Segmented, Select, Upload } from 'antd'
|
import { Button, Empty, InputNumber, Segmented, Select, Upload } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import React, { FC } from 'react'
|
import React, { FC } from 'react'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
@ -47,6 +47,7 @@ const logger = loggerService.withContext('NewApiPage')
|
|||||||
const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||||
const [mode, setMode] = useState<keyof PaintingsState>('openai_image_generate')
|
const [mode, setMode] = useState<keyof PaintingsState>('openai_image_generate')
|
||||||
const { addPainting, removePainting, updatePainting, openai_image_generate, openai_image_edit } = usePaintings()
|
const { addPainting, removePainting, updatePainting, openai_image_generate, openai_image_edit } = usePaintings()
|
||||||
|
const { ProviderAvatar } = useProviderAvatar()
|
||||||
|
|
||||||
const newApiPaintings = useMemo(() => {
|
const newApiPaintings = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -509,12 +510,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
||||||
<SettingHelpLink target="_blank" href={'https://docs.newapi.pro/apps/cherry-studio/'}>
|
<SettingHelpLink target="_blank" href={'https://docs.newapi.pro/apps/cherry-studio/'}>
|
||||||
{t('paintings.learn_more')}
|
{t('paintings.learn_more')}
|
||||||
<ProviderLogo
|
<ProviderAvatar pid={newApiProvider.id} size={16} style={{ marginLeft: 5 }} />
|
||||||
shape="square"
|
|
||||||
src={getProviderLogo(newApiProvider.id)}
|
|
||||||
size={16}
|
|
||||||
style={{ marginLeft: 5 }}
|
|
||||||
/>
|
|
||||||
</SettingHelpLink>
|
</SettingHelpLink>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
|
|
||||||
@ -525,7 +521,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
{providerOptions.map((provider) => (
|
{providerOptions.map((provider) => (
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
<Select.Option value={provider.value} key={provider.value}>
|
||||||
<SelectOptionContainer>
|
<SelectOptionContainer>
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
<ProviderAvatar pid={provider.value} size={16} />
|
||||||
{provider.label}
|
{provider.label}
|
||||||
</SelectOptionContainer>
|
</SelectOptionContainer>
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
@ -794,10 +790,6 @@ const ToolbarMenu = styled.div`
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
|
||||||
border: 0.5px solid var(--color-border);
|
|
||||||
`
|
|
||||||
|
|
||||||
// 添加新的样式组件
|
// 添加新的样式组件
|
||||||
const ModeSegmentedContainer = styled.div`
|
const ModeSegmentedContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -4,10 +4,10 @@ import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navb
|
|||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import TranslateButton from '@renderer/components/TranslateButton'
|
import TranslateButton from '@renderer/components/TranslateButton'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
|
||||||
import { LanguagesEnum } from '@renderer/config/translate'
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import { useProviderAvatar } from '@renderer/hooks/useProviderLogo'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
@ -17,7 +17,7 @@ import { useAppDispatch } from '@renderer/store'
|
|||||||
import { setGenerating } from '@renderer/store/runtime'
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
import type { TokenFluxPainting } from '@renderer/types'
|
import type { TokenFluxPainting } from '@renderer/types'
|
||||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||||
import { Avatar, Button, Select, Tooltip } from 'antd'
|
import { Button, Select, Tooltip } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { Info } from 'lucide-react'
|
import { Info } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
@ -50,6 +50,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const providers = useAllProviders()
|
const providers = useAllProviders()
|
||||||
const { addPainting, removePainting, updatePainting, tokenflux_paintings } = usePaintings()
|
const { addPainting, removePainting, updatePainting, tokenflux_paintings } = usePaintings()
|
||||||
|
const { ProviderAvatar } = useProviderAvatar()
|
||||||
const tokenFluxPaintings = tokenflux_paintings
|
const tokenFluxPaintings = tokenflux_paintings
|
||||||
const [painting, setPainting] = useState<TokenFluxPainting>(
|
const [painting, setPainting] = useState<TokenFluxPainting>(
|
||||||
tokenFluxPaintings[0] || { ...DEFAULT_TOKENFLUX_PAINTING, id: uuid() }
|
tokenFluxPaintings[0] || { ...DEFAULT_TOKENFLUX_PAINTING, id: uuid() }
|
||||||
@ -383,7 +384,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
<SettingTitle style={{ marginBottom: 8 }}>{t('common.provider')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 8 }}>{t('common.provider')}</SettingTitle>
|
||||||
<SettingHelpLink target="_blank" href="https://tokenflux.ai">
|
<SettingHelpLink target="_blank" href="https://tokenflux.ai">
|
||||||
{t('paintings.learn_more')}
|
{t('paintings.learn_more')}
|
||||||
<ProviderLogo shape="square" src={getProviderLogo('tokenflux')} size={16} style={{ marginLeft: 5 }} />
|
<ProviderAvatar pid={'tokenflux'} size={16} style={{ marginLeft: 5 }} />
|
||||||
</SettingHelpLink>
|
</SettingHelpLink>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
|
|
||||||
@ -394,7 +395,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
{providerOptions.map((provider) => (
|
{providerOptions.map((provider) => (
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
<Select.Option value={provider.value} key={provider.value}>
|
||||||
<SelectOptionContainer>
|
<SelectOptionContainer>
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
<ProviderAvatar pid={provider.value} size={16} />
|
||||||
{provider.label}
|
{provider.label}
|
||||||
</SelectOptionContainer>
|
</SelectOptionContainer>
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
@ -765,10 +766,6 @@ const InfoIcon = styled(Info)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
|
||||||
border: 0.5px solid var(--color-border);
|
|
||||||
`
|
|
||||||
|
|
||||||
const ProviderTitleContainer = styled.div`
|
const ProviderTitleContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@ -4,16 +4,16 @@ import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navb
|
|||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
|
||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import { useProviderAvatar } from '@renderer/hooks/useProviderLogo'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setGenerating } from '@renderer/store/runtime'
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||||
import { Avatar, Button, InputNumber, Radio, Select } from 'antd'
|
import { Button, InputNumber, Radio, Select } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -39,6 +39,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const [painting, setPainting] = useState<any>(zhipu_paintings?.[0] || DEFAULT_PAINTING)
|
const [painting, setPainting] = useState<any>(zhipu_paintings?.[0] || DEFAULT_PAINTING)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const providers = useAllProviders()
|
const providers = useAllProviders()
|
||||||
|
const { ProviderAvatar } = useProviderAvatar()
|
||||||
|
|
||||||
// 确保painting使用智谱的cogview系列模型
|
// 确保painting使用智谱的cogview系列模型
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -368,19 +369,14 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
<SettingHelpLink target="_blank" href={COURSE_URL}>
|
<SettingHelpLink target="_blank" href={COURSE_URL}>
|
||||||
{t('paintings.paint_course')}
|
{t('paintings.paint_course')}
|
||||||
</SettingHelpLink>
|
</SettingHelpLink>
|
||||||
<ProviderLogo
|
<ProviderAvatar pid={zhipuProvider.id} size={16} style={{ marginLeft: 5 }} />
|
||||||
shape="square"
|
|
||||||
src={getProviderLogo(zhipuProvider.id)}
|
|
||||||
size={16}
|
|
||||||
style={{ marginLeft: 5 }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
<Select value={providerOptions[0].value} onChange={handleProviderChange} style={{ marginBottom: 15 }}>
|
<Select value={providerOptions[0].value} onChange={handleProviderChange} style={{ marginBottom: 15 }}>
|
||||||
{providerOptions.map((provider) => (
|
{providerOptions.map((provider) => (
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
<Select.Option value={provider.value} key={provider.value}>
|
||||||
<SelectOptionContainer>
|
<SelectOptionContainer>
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
<ProviderAvatar pid={provider.value} size={16} />
|
||||||
{provider.label}
|
{provider.label}
|
||||||
</SelectOptionContainer>
|
</SelectOptionContainer>
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
@ -581,8 +577,4 @@ const SelectOptionContainer = styled.div`
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
|
||||||
border-radius: 4px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default ZhipuPage
|
export default ZhipuPage
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import { loggerService } from '@logger'
|
|
||||||
import { Center, VStack } from '@renderer/components/Layout'
|
import { Center, VStack } from '@renderer/components/Layout'
|
||||||
import ProviderLogoPicker from '@renderer/components/ProviderLogoPicker'
|
import ProviderLogoPicker from '@renderer/components/ProviderLogoPicker'
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
||||||
|
import { useProviderAvatar } from '@renderer/hooks/useProviderLogo'
|
||||||
import ImageStorage from '@renderer/services/ImageStorage'
|
import ImageStorage from '@renderer/services/ImageStorage'
|
||||||
import { Provider, ProviderType } from '@renderer/types'
|
import { Provider, ProviderType } from '@renderer/types'
|
||||||
import { compressImage, generateColorFromChar, getForegroundColor } from '@renderer/utils'
|
import { compressImage } from '@renderer/utils'
|
||||||
import { Divider, Dropdown, Form, Input, Modal, Popover, Select, Upload } from 'antd'
|
import { Divider, Dropdown, Form, Input, Modal, Popover, Select, Upload } from 'antd'
|
||||||
import { ItemType } from 'antd/es/menu/interface'
|
import { ItemType } from 'antd/es/menu/interface'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
const logger = loggerService.withContext('AddProviderPopup')
|
// const logger = loggerService.withContext('AddProviderPopup')
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
provider?: Provider
|
provider?: Provider
|
||||||
@ -23,27 +23,20 @@ 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 [logoPickerOpen, setLogoPickerOpen] = useState(false)
|
const [logoPickerOpen, setLogoPickerOpen] = useState(false)
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false)
|
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const uploadRef = useRef<HTMLDivElement>(null)
|
const uploadRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [logo, setLogo] = useState<string>()
|
||||||
|
|
||||||
|
const { ProviderAvatar, logos, saveLogo } = useProviderAvatar()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (provider?.id) {
|
if (provider) {
|
||||||
const loadLogo = async () => {
|
const logo = logos[provider.id]
|
||||||
try {
|
setLogo(logo)
|
||||||
const logoData = await ImageStorage.get(`provider-${provider.id}`)
|
|
||||||
if (logoData) {
|
|
||||||
setLogo(logoData)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to load logo', error as Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadLogo()
|
|
||||||
}
|
}
|
||||||
}, [provider])
|
}, [provider, logos, setLogo])
|
||||||
|
|
||||||
const onOk = async () => {
|
const onOk = async () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@ -74,12 +67,9 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
const logoUrl = PROVIDER_LOGO_MAP[providerId]
|
const logoUrl = PROVIDER_LOGO_MAP[providerId]
|
||||||
|
|
||||||
if (provider?.id) {
|
if (provider?.id) {
|
||||||
await ImageStorage.set(`provider-${provider.id}`, logoUrl)
|
saveLogo(logoUrl, provider.id)
|
||||||
const savedLogo = await ImageStorage.get(`provider-${provider.id}`)
|
|
||||||
setLogo(savedLogo)
|
|
||||||
} else {
|
|
||||||
setLogo(logoUrl)
|
|
||||||
}
|
}
|
||||||
|
setLogo(logoUrl)
|
||||||
|
|
||||||
setLogoPickerOpen(false)
|
setLogoPickerOpen(false)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -89,10 +79,9 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
|
|
||||||
const handleReset = async () => {
|
const handleReset = async () => {
|
||||||
try {
|
try {
|
||||||
setLogo(null)
|
|
||||||
|
|
||||||
if (provider?.id) {
|
if (provider?.id) {
|
||||||
await ImageStorage.set(`provider-${provider.id}`, '')
|
saveLogo('', provider.id)
|
||||||
|
ImageStorage.set(`provider-${provider.id}`, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
setDropdownOpen(false)
|
setDropdownOpen(false)
|
||||||
@ -101,10 +90,6 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInitials = () => {
|
|
||||||
return name.charAt(0) || 'P'
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
key: 'upload',
|
key: 'upload',
|
||||||
@ -133,7 +118,7 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
await ImageStorage.set(`provider-${provider.id}`, logoData)
|
await ImageStorage.set(`provider-${provider.id}`, logoData)
|
||||||
}
|
}
|
||||||
const savedLogo = await ImageStorage.get(`provider-${provider.id}`)
|
const savedLogo = await ImageStorage.get(`provider-${provider.id}`)
|
||||||
setLogo(savedLogo)
|
saveLogo(savedLogo, provider.id)
|
||||||
} else {
|
} else {
|
||||||
// 临时保存在内存中,等创建 provider 后会在调用方保存
|
// 临时保存在内存中,等创建 provider 后会在调用方保存
|
||||||
const tempUrl = await new Promise<string>((resolve) => {
|
const tempUrl = await new Promise<string>((resolve) => {
|
||||||
@ -171,10 +156,6 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
}
|
}
|
||||||
] satisfies ItemType[]
|
] satisfies ItemType[]
|
||||||
|
|
||||||
// for logo
|
|
||||||
const backgroundColor = generateColorFromChar(name)
|
|
||||||
const color = name ? getForegroundColor(backgroundColor) : 'white'
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
@ -214,13 +195,9 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placement="bottom">
|
placement="bottom">
|
||||||
{logo ? (
|
<ProviderLogo>
|
||||||
<ProviderLogo src={logo} />
|
<ProviderAvatar pid={provider?.id} name={name} src={logo} size={60} style={{ fontSize: 32 }} />
|
||||||
) : (
|
</ProviderLogo>
|
||||||
<ProviderInitialsLogo style={name ? { backgroundColor, color } : undefined}>
|
|
||||||
{getInitials()}
|
|
||||||
</ProviderInitialsLogo>
|
|
||||||
)}
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</VStack>
|
</VStack>
|
||||||
@ -258,39 +235,6 @@ 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;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const MenuItem = styled.div`
|
const MenuItem = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -321,3 +265,15 @@ export default class AddProviderPopup {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ProviderLogo = styled.div`
|
||||||
|
cursor: pointer;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
border: 0.5px solid var(--color-border);
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@ -5,22 +5,13 @@ import {
|
|||||||
type DraggableVirtualListRef,
|
type DraggableVirtualListRef,
|
||||||
useDraggableReorder
|
useDraggableReorder
|
||||||
} from '@renderer/components/DraggableList'
|
} from '@renderer/components/DraggableList'
|
||||||
import { DeleteIcon, EditIcon, PoeLogo } from '@renderer/components/Icons'
|
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
|
||||||
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import { useProviderAvatar } from '@renderer/hooks/useProviderLogo'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import ImageStorage from '@renderer/services/ImageStorage'
|
|
||||||
import { isSystemProvider, Provider, ProviderType } from '@renderer/types'
|
import { isSystemProvider, Provider, ProviderType } from '@renderer/types'
|
||||||
import {
|
import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils'
|
||||||
generateColorFromChar,
|
import { Button, Dropdown, Input, MenuProps, Tag } from 'antd'
|
||||||
getFancyProviderName,
|
|
||||||
getFirstCharacter,
|
|
||||||
getForegroundColor,
|
|
||||||
matchKeywordsInModel,
|
|
||||||
matchKeywordsInProvider,
|
|
||||||
uuid
|
|
||||||
} from '@renderer/utils'
|
|
||||||
import { Avatar, Button, Dropdown, Input, MenuProps, Tag } from 'antd'
|
|
||||||
import { GripVertical, PlusIcon, Search, UserPen } from 'lucide-react'
|
import { GripVertical, PlusIcon, Search, UserPen } from 'lucide-react'
|
||||||
import { FC, startTransition, useCallback, useEffect, useRef, useState } from 'react'
|
import { FC, startTransition, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -45,34 +36,13 @@ const ProviderList: 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>>({})
|
|
||||||
const listRef = useRef<DraggableVirtualListRef>(null)
|
const listRef = useRef<DraggableVirtualListRef>(null)
|
||||||
|
const { ProviderAvatar, saveLogo, deleteLogo } = useProviderAvatar()
|
||||||
|
|
||||||
const setSelectedProvider = useCallback((provider: Provider) => {
|
const setSelectedProvider = useCallback((provider: Provider) => {
|
||||||
startTransition(() => _setSelectedProvider(provider))
|
startTransition(() => _setSelectedProvider(provider))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
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) {
|
|
||||||
logger.error(`Failed to load logo for provider ${provider.id}`, error as Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setProviderLogos(logos)
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAllLogos()
|
|
||||||
}, [providers])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchParams.get('id')) {
|
if (searchParams.get('id')) {
|
||||||
const providerId = searchParams.get('id')
|
const providerId = searchParams.get('id')
|
||||||
@ -162,17 +132,10 @@ const ProviderList: FC = () => {
|
|||||||
models: [],
|
models: [],
|
||||||
enabled: true,
|
enabled: true,
|
||||||
isSystem: false
|
isSystem: false
|
||||||
} as Provider
|
} satisfies Provider
|
||||||
|
|
||||||
let updatedLogos = { ...providerLogos }
|
|
||||||
if (logo) {
|
if (logo) {
|
||||||
try {
|
try {
|
||||||
await ImageStorage.set(`provider-${provider.id}`, logo)
|
saveLogo(logo, provider.id)
|
||||||
updatedLogos = {
|
|
||||||
...updatedLogos,
|
|
||||||
[provider.id]: logo
|
|
||||||
}
|
|
||||||
setProviderLogos(updatedLogos)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to save logo', error as Error)
|
logger.error('Failed to save logo', error as Error)
|
||||||
window.message.error('保存Provider Logo失败')
|
window.message.error('保存Provider Logo失败')
|
||||||
@ -183,132 +146,91 @@ const ProviderList: FC = () => {
|
|||||||
setSelectedProvider(provider)
|
setSelectedProvider(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDropdownMenus = (provider: Provider): MenuProps['items'] => {
|
const getDropdownMenus = useCallback(
|
||||||
const noteMenu = {
|
(provider: Provider): MenuProps['items'] => {
|
||||||
label: t('settings.provider.notes.title'),
|
const noteMenu = {
|
||||||
key: 'notes',
|
label: t('settings.provider.notes.title'),
|
||||||
icon: <UserPen size={14} />,
|
key: 'notes',
|
||||||
onClick: () => ModelNotesPopup.show({ provider })
|
icon: <UserPen size={14} />,
|
||||||
}
|
onClick: () => ModelNotesPopup.show({ provider })
|
||||||
|
}
|
||||||
|
|
||||||
const editMenu = {
|
const editMenu = {
|
||||||
label: t('common.edit'),
|
label: t('common.edit'),
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
icon: <EditIcon size={14} />,
|
icon: <EditIcon size={14} />,
|
||||||
async onClick() {
|
async onClick() {
|
||||||
const { name, type, logoFile, logo } = await AddProviderPopup.show(provider)
|
const { name, type, logoFile, logo } = await AddProviderPopup.show(provider)
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
updateProvider({ ...provider, name, type })
|
updateProvider({ ...provider, name, type })
|
||||||
if (provider.id) {
|
if (provider.id) {
|
||||||
if (logo) {
|
if (logo) {
|
||||||
try {
|
try {
|
||||||
await ImageStorage.set(`provider-${provider.id}`, logo)
|
saveLogo(logo, provider.id)
|
||||||
setProviderLogos((prev) => ({
|
} catch (error) {
|
||||||
...prev,
|
logger.error('Failed to save logo', error as Error)
|
||||||
[provider.id]: logo
|
window.message.error('更新Provider Logo失败')
|
||||||
}))
|
}
|
||||||
} catch (error) {
|
} else if (logo === undefined && logoFile === undefined) {
|
||||||
logger.error('Failed to save logo', error as Error)
|
try {
|
||||||
window.message.error('更新Provider Logo失败')
|
deleteLogo(provider.id)
|
||||||
}
|
} catch (error) {
|
||||||
} else if (logo === undefined && logoFile === undefined) {
|
logger.error('Failed to reset logo', error as Error)
|
||||||
try {
|
}
|
||||||
await ImageStorage.set(`provider-${provider.id}`, '')
|
|
||||||
setProviderLogos((prev) => {
|
|
||||||
const newLogos = { ...prev }
|
|
||||||
delete newLogos[provider.id]
|
|
||||||
return newLogos
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to reset logo', error as Error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const deleteMenu = {
|
const deleteMenu = {
|
||||||
label: t('common.delete'),
|
label: t('common.delete'),
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
icon: <DeleteIcon size={14} className="lucide-custom" />,
|
icon: <DeleteIcon size={14} className="lucide-custom" />,
|
||||||
danger: true,
|
danger: true,
|
||||||
async onClick() {
|
async onClick() {
|
||||||
window.modal.confirm({
|
window.modal.confirm({
|
||||||
title: t('settings.provider.delete.title'),
|
title: t('settings.provider.delete.title'),
|
||||||
content: t('settings.provider.delete.content'),
|
content: t('settings.provider.delete.content'),
|
||||||
okButtonProps: { danger: true },
|
okButtonProps: { danger: true },
|
||||||
okText: t('common.delete'),
|
okText: t('common.delete'),
|
||||||
centered: true,
|
centered: true,
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
// 删除provider前先清理其logo
|
// 删除provider前先清理其logo
|
||||||
if (provider.id) {
|
if (provider.id) {
|
||||||
try {
|
try {
|
||||||
await ImageStorage.remove(`provider-${provider.id}`)
|
deleteLogo(provider.id)
|
||||||
setProviderLogos((prev) => {
|
} catch (error) {
|
||||||
const newLogos = { ...prev }
|
logger.error('Failed to delete logo', error as Error)
|
||||||
delete newLogos[provider.id]
|
}
|
||||||
return newLogos
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to delete logo', error as Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSelectedProvider(providers.filter((p) => isSystemProvider(p))[0])
|
||||||
|
removeProvider(provider)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
setSelectedProvider(providers.filter((p) => isSystemProvider(p))[0])
|
}
|
||||||
removeProvider(provider)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const menus = [editMenu, noteMenu, deleteMenu]
|
const menus = [editMenu, noteMenu, deleteMenu]
|
||||||
|
|
||||||
if (providers.filter((p) => p.id === provider.id).length > 1) {
|
if (providers.filter((p) => p.id === provider.id).length > 1) {
|
||||||
return menus
|
return menus
|
||||||
}
|
|
||||||
|
|
||||||
if (isSystemProvider(provider)) {
|
|
||||||
return [noteMenu]
|
|
||||||
} else if (provider.isSystem) {
|
|
||||||
// 这里是处理数据中存在新版本删掉的系统提供商的情况
|
|
||||||
// 未来期望能重构一下,不要依赖isSystem字段
|
|
||||||
return [noteMenu, deleteMenu]
|
|
||||||
} else {
|
|
||||||
return menus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getProviderAvatar = (provider: Provider, size: number = 25) => {
|
|
||||||
// 特殊处理一下svg格式
|
|
||||||
if (isSystemProvider(provider)) {
|
|
||||||
switch (provider.id) {
|
|
||||||
case 'poe':
|
|
||||||
return <PoeLogo fontSize={size} />
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const logoSrc = getProviderLogo(provider.id)
|
if (isSystemProvider(provider)) {
|
||||||
if (logoSrc) {
|
return [noteMenu]
|
||||||
return <ProviderLogo draggable="false" shape="circle" src={logoSrc} size={size} />
|
} else if (provider.isSystem) {
|
||||||
}
|
// 这里是处理数据中存在新版本删掉的系统提供商的情况
|
||||||
|
// 未来期望能重构一下,不要依赖isSystem字段
|
||||||
const customLogo = providerLogos[provider.id]
|
return [noteMenu, deleteMenu]
|
||||||
if (customLogo) {
|
} else {
|
||||||
return <ProviderLogo draggable="false" shape="square" src={customLogo} size={size} />
|
return menus
|
||||||
}
|
}
|
||||||
|
},
|
||||||
// generate color for custom provider
|
[providers, deleteLogo, removeProvider, saveLogo, setSelectedProvider, t, updateProvider]
|
||||||
const backgroundColor = generateColorFromChar(provider.name)
|
)
|
||||||
const color = provider.name ? getForegroundColor(backgroundColor) : 'white'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProviderLogo size={size} shape="square" style={{ backgroundColor, color, minWidth: size }}>
|
|
||||||
{getFirstCharacter(provider.name)}
|
|
||||||
</ProviderLogo>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredProviders = providers.filter((provider) => {
|
const filteredProviders = providers.filter((provider) => {
|
||||||
const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
||||||
@ -336,6 +258,31 @@ const ProviderList: FC = () => {
|
|||||||
[handleReorder]
|
[handleReorder]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const providerRender = useCallback(
|
||||||
|
(provider: Provider) => {
|
||||||
|
return (
|
||||||
|
<Dropdown menu={{ items: getDropdownMenus(provider) }} trigger={['contextMenu']}>
|
||||||
|
<ProviderListItem
|
||||||
|
key={provider.id}
|
||||||
|
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
||||||
|
onClick={() => setSelectedProvider(provider)}>
|
||||||
|
<DragHandle>
|
||||||
|
<GripVertical size={12} />
|
||||||
|
</DragHandle>
|
||||||
|
<ProviderAvatar pid={provider.id} name={provider.name} size={25} />
|
||||||
|
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
|
||||||
|
{provider.enabled && (
|
||||||
|
<Tag color="green" style={{ marginLeft: 'auto', marginRight: 0, borderRadius: 16 }}>
|
||||||
|
ON
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</ProviderListItem>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[ProviderAvatar, getDropdownMenus, selectedProvider?.id, setSelectedProvider]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="selectable">
|
<Container className="selectable">
|
||||||
<ProviderListContainer>
|
<ProviderListContainer>
|
||||||
@ -373,25 +320,7 @@ const ProviderList: FC = () => {
|
|||||||
paddingRight: 5
|
paddingRight: 5
|
||||||
}}
|
}}
|
||||||
itemContainerStyle={{ paddingBottom: 5 }}>
|
itemContainerStyle={{ paddingBottom: 5 }}>
|
||||||
{(provider) => (
|
{providerRender}
|
||||||
<Dropdown menu={{ items: getDropdownMenus(provider) }} trigger={['contextMenu']}>
|
|
||||||
<ProviderListItem
|
|
||||||
key={provider.id}
|
|
||||||
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
|
||||||
onClick={() => setSelectedProvider(provider)}>
|
|
||||||
<DragHandle>
|
|
||||||
<GripVertical size={12} />
|
|
||||||
</DragHandle>
|
|
||||||
{getProviderAvatar(provider)}
|
|
||||||
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
|
|
||||||
{provider.enabled && (
|
|
||||||
<Tag color="green" style={{ marginLeft: 'auto', marginRight: 0, borderRadius: 16 }}>
|
|
||||||
ON
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</ProviderListItem>
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
|
||||||
</DraggableVirtualList>
|
</DraggableVirtualList>
|
||||||
<AddButtonWrapper>
|
<AddButtonWrapper>
|
||||||
<Button
|
<Button
|
||||||
@ -466,10 +395,6 @@ const DragHandle = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
|
||||||
border: 0.5px solid var(--color-border);
|
|
||||||
`
|
|
||||||
|
|
||||||
const ProviderItemName = styled.div`
|
const ProviderItemName = styled.div`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default class ImageStorage {
|
|||||||
db.settings.update(id, { value })
|
db.settings.update(id, { value })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await db.settings.add({ id, value })
|
await db.settings.put({ id, value })
|
||||||
} else {
|
} else {
|
||||||
// file image
|
// file image
|
||||||
const base64Image = await convertToBase64(value)
|
const base64Image = await convertToBase64(value)
|
||||||
@ -25,7 +25,7 @@ export default class ImageStorage {
|
|||||||
db.settings.update(id, { value: base64Image })
|
db.settings.update(id, { value: base64Image })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await db.settings.add({ id, value: base64Image })
|
await db.settings.put({ id, value: base64Image })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -233,7 +233,8 @@ vi.mock('@renderer/store/llm.ts', () => {
|
|||||||
secretAccessKey: '',
|
secretAccessKey: '',
|
||||||
region: ''
|
region: ''
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
logos: {}
|
||||||
} satisfies LlmState
|
} satisfies LlmState
|
||||||
|
|
||||||
const mockReducer = (state = mockInitialState) => {
|
const mockReducer = (state = mockInitialState) => {
|
||||||
|
|||||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 143,
|
version: 144,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export interface LlmState {
|
|||||||
translateModel: Model
|
translateModel: Model
|
||||||
quickAssistantId: string
|
quickAssistantId: string
|
||||||
settings: LlmSettings
|
settings: LlmSettings
|
||||||
|
logos: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialState: LlmState = {
|
export const initialState: LlmState = {
|
||||||
@ -71,7 +72,8 @@ export const initialState: LlmState = {
|
|||||||
secretAccessKey: '',
|
secretAccessKey: '',
|
||||||
region: ''
|
region: ''
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
logos: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 由于 isLocalAi 目前总是为false,该函数暂未被使用
|
// 由于 isLocalAi 目前总是为false,该函数暂未被使用
|
||||||
@ -219,6 +221,17 @@ const llmSlice = createSlice({
|
|||||||
provider.models[modelIndex] = action.payload.model
|
provider.models[modelIndex] = action.payload.model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
setLogos: (state, action: PayloadAction<Record<string, string>>) => {
|
||||||
|
state.logos = action.payload
|
||||||
|
},
|
||||||
|
setLogo: (state, action: PayloadAction<{ id: string; logo: string }>) => {
|
||||||
|
const { id, logo } = action.payload
|
||||||
|
state.logos[id] = logo
|
||||||
|
},
|
||||||
|
removeLogo: (state, action: PayloadAction<string>) => {
|
||||||
|
const id = action.payload
|
||||||
|
delete state.logos[id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -244,7 +257,10 @@ export const {
|
|||||||
setAwsBedrockAccessKeyId,
|
setAwsBedrockAccessKeyId,
|
||||||
setAwsBedrockSecretAccessKey,
|
setAwsBedrockSecretAccessKey,
|
||||||
setAwsBedrockRegion,
|
setAwsBedrockRegion,
|
||||||
updateModel
|
updateModel,
|
||||||
|
setLogos,
|
||||||
|
setLogo,
|
||||||
|
removeLogo
|
||||||
} = llmSlice.actions
|
} = llmSlice.actions
|
||||||
|
|
||||||
export default llmSlice.reducer
|
export default llmSlice.reducer
|
||||||
|
|||||||
@ -2322,6 +2322,15 @@ const migrateConfig = {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// after 1.5.8
|
||||||
|
'144': (state: RootState) => {
|
||||||
|
try {
|
||||||
|
state.llm.logos = {}
|
||||||
|
return state
|
||||||
|
} catch (error) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user