mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 22:52:08 +08:00
feat: support NewAPI as a generic provider type (#10696)
* feat: add support for New API providerType * feat: support New API as a generic painting provider * refactor: update styling in painting pages to use Tailwind classes - Replaced inline styles with Tailwind CSS classes for margin adjustments in AihubmixPage, DmxapiPage, SiliconPage, TokenFluxPage, and ZhipuPage. - Enhanced consistency and maintainability of the codebase by standardizing styling approach across components. - Minor refactor in ProviderSelect component to support className prop for better styling flexibility.
This commit is contained in:
parent
726b2570e2
commit
0502ff48f1
@ -62,13 +62,14 @@ function handleSpecialProviders(model: Model, provider: Provider): Provider {
|
|||||||
// return createVertexProvider(provider)
|
// return createVertexProvider(provider)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if (isNewApiProvider(provider)) {
|
||||||
|
return newApiResolverCreator(model, provider)
|
||||||
|
}
|
||||||
|
|
||||||
if (isSystemProvider(provider)) {
|
if (isSystemProvider(provider)) {
|
||||||
if (provider.id === 'aihubmix') {
|
if (provider.id === 'aihubmix') {
|
||||||
return aihubmixProviderCreator(model, provider)
|
return aihubmixProviderCreator(model, provider)
|
||||||
}
|
}
|
||||||
if (isNewApiProvider(provider)) {
|
|
||||||
return newApiResolverCreator(model, provider)
|
|
||||||
}
|
|
||||||
if (provider.id === 'vertexai') {
|
if (provider.id === 'vertexai') {
|
||||||
return vertexAnthropicProviderCreator(model, provider)
|
return vertexAnthropicProviderCreator(model, provider)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -283,7 +283,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
'new-api': {
|
'new-api': {
|
||||||
id: 'new-api',
|
id: 'new-api',
|
||||||
name: 'New API',
|
name: 'New API',
|
||||||
type: 'openai',
|
type: 'new-api',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: 'http://localhost:3000',
|
apiHost: 'http://localhost:3000',
|
||||||
models: SYSTEM_MODELS['new-api'],
|
models: SYSTEM_MODELS['new-api'],
|
||||||
@ -1422,5 +1422,5 @@ export const isGeminiWebSearchProvider = (provider: Provider) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const isNewApiProvider = (provider: Provider) => {
|
export const isNewApiProvider = (provider: Provider) => {
|
||||||
return ['new-api', 'cherryin'].includes(provider.id)
|
return ['new-api', 'cherryin'].includes(provider.id) || provider.type === 'new-api'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
|
|||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
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 FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
@ -35,6 +34,7 @@ import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
|||||||
import { SettingHelpLink, SettingTitle } from '../settings'
|
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||||
import Artboard from './components/Artboard'
|
import Artboard from './components/Artboard'
|
||||||
import PaintingsList from './components/PaintingsList'
|
import PaintingsList from './components/PaintingsList'
|
||||||
|
import ProviderSelect from './components/ProviderSelect'
|
||||||
import { type ConfigItem, createModeConfigs, DEFAULT_PAINTING } from './config/aihubmixConfig'
|
import { type ConfigItem, createModeConfigs, DEFAULT_PAINTING } from './config/aihubmixConfig'
|
||||||
import { checkProviderEnabled } from './utils'
|
import { checkProviderEnabled } from './utils'
|
||||||
|
|
||||||
@ -76,20 +76,6 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const providers = useAllProviders()
|
const providers = useAllProviders()
|
||||||
const providerOptions = Options.map((option) => {
|
|
||||||
const provider = providers.find((p) => p.id === option)
|
|
||||||
if (provider) {
|
|
||||||
return {
|
|
||||||
label: getProviderLabel(provider.id),
|
|
||||||
value: provider.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
label: 'Unknown Provider',
|
|
||||||
value: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { generating } = useRuntime()
|
const { generating } = useRuntime()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -849,17 +835,12 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
/>
|
/>
|
||||||
</SettingHelpLink>
|
</SettingHelpLink>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
|
<ProviderSelect
|
||||||
<Select value={providerOptions[1].value} onChange={handleProviderChange} style={{ marginBottom: 15 }}>
|
provider={aihubmixProvider}
|
||||||
{providerOptions.map((provider) => (
|
options={Options}
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
onChange={handleProviderChange}
|
||||||
<SelectOptionContainer>
|
className={'mb-4'}
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
/>
|
||||||
{provider.label}
|
|
||||||
</SelectOptionContainer>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
{/* 使用JSON配置渲染设置项 */}
|
{/* 使用JSON配置渲染设置项 */}
|
||||||
{modeConfigs[mode].filter((item) => (item.condition ? item.condition(painting) : true)).map(renderConfigItem)}
|
{modeConfigs[mode].filter((item) => (item.condition ? item.condition(painting) : true)).map(renderConfigItem)}
|
||||||
@ -1034,12 +1015,6 @@ const ModeSegmentedContainer = styled.div`
|
|||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SelectOptionContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
`
|
|
||||||
|
|
||||||
// 添加新的样式组件
|
// 添加新的样式组件
|
||||||
const ProviderTitleContainer = styled.div`
|
const ProviderTitleContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -8,7 +8,6 @@ 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 { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
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'
|
||||||
@ -29,6 +28,7 @@ import { SettingHelpLink, SettingTitle } from '../settings'
|
|||||||
import Artboard from './components/Artboard'
|
import Artboard from './components/Artboard'
|
||||||
import ImageUploader from './components/ImageUploader'
|
import ImageUploader from './components/ImageUploader'
|
||||||
import PaintingsList from './components/PaintingsList'
|
import PaintingsList from './components/PaintingsList'
|
||||||
|
import ProviderSelect from './components/ProviderSelect'
|
||||||
import {
|
import {
|
||||||
COURSE_URL,
|
COURSE_URL,
|
||||||
DEFAULT_PAINTING,
|
DEFAULT_PAINTING,
|
||||||
@ -46,20 +46,6 @@ 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 providerOptions = Options.map((option) => {
|
|
||||||
const provider = providers.find((p) => p.id === option)
|
|
||||||
if (provider) {
|
|
||||||
return {
|
|
||||||
label: getProviderLabel(provider.id),
|
|
||||||
value: provider.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
label: 'Unknown Provider',
|
|
||||||
value: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const dmxapiProvider = providers.find((p) => p.id === 'dmxapi')!
|
const dmxapiProvider = providers.find((p) => p.id === 'dmxapi')!
|
||||||
|
|
||||||
@ -785,9 +771,9 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('paintings.title')}</NavbarCenter>
|
<NavbarCenter className="border-r-0">{t('paintings.title')}</NavbarCenter>
|
||||||
{isMac && (
|
{isMac && (
|
||||||
<NavbarRight style={{ justifyContent: 'flex-end' }}>
|
<NavbarRight className="justify-end">
|
||||||
<Button size="small" className="nodrag" icon={<PlusOutlined />} onClick={createNewPainting}>
|
<Button size="small" className="nodrag" icon={<PlusOutlined />} onClick={createNewPainting}>
|
||||||
{t('paintings.button.new.image')}
|
{t('paintings.button.new.image')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -797,7 +783,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
<ContentContainer id="content-container">
|
<ContentContainer id="content-container">
|
||||||
<LeftContainer>
|
<LeftContainer>
|
||||||
<ProviderTitleContainer>
|
<ProviderTitleContainer>
|
||||||
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
<SettingTitle className="mb-1">{t('common.provider')}</SettingTitle>
|
||||||
<div>
|
<div>
|
||||||
<SettingHelpLink target="_blank" href={COURSE_URL}>
|
<SettingHelpLink target="_blank" href={COURSE_URL}>
|
||||||
{t('paintings.paint_course')}
|
{t('paintings.paint_course')}
|
||||||
@ -805,28 +791,19 @@ 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
|
<ProviderLogo shape="square" src={getProviderLogo(dmxapiProvider.id)} size={16} className="ml-1" />
|
||||||
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 }}>
|
<ProviderSelect
|
||||||
{providerOptions.map((provider) => (
|
provider={dmxapiProvider}
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
options={Options}
|
||||||
<SelectOptionContainer>
|
onChange={handleProviderChange}
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
className="mb-4"
|
||||||
{provider.label}
|
/>
|
||||||
</SelectOptionContainer>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{painting.generationMode &&
|
{painting.generationMode &&
|
||||||
[generationModeType.EDIT, generationModeType.MERGE].includes(painting.generationMode) && (
|
[generationModeType.EDIT, generationModeType.MERGE].includes(painting.generationMode) && (
|
||||||
<>
|
<>
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>参考图</SettingTitle>
|
<SettingTitle className="mt-4 mb-1">参考图</SettingTitle>
|
||||||
<ImageUploader
|
<ImageUploader
|
||||||
fileMap={fileMap}
|
fileMap={fileMap}
|
||||||
maxImages={painting.generationMode === generationModeType.EDIT ? 1 : 3}
|
maxImages={painting.generationMode === generationModeType.EDIT ? 1 : 3}
|
||||||
@ -838,13 +815,13 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
<SettingTitle className="mt-4 mb-1">
|
||||||
{t('common.model')} <SettingPrice>{painting.priceModel !== '0' ? painting.priceModel : ''}</SettingPrice>
|
{t('common.model')} <SettingPrice>{painting.priceModel !== '0' ? painting.priceModel : ''}</SettingPrice>
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
<Select
|
<Select
|
||||||
value={painting.model}
|
value={painting.model}
|
||||||
onChange={onSelectModel}
|
onChange={onSelectModel}
|
||||||
style={{ width: '100%' }}
|
className="w-full"
|
||||||
loading={isLoadingModels}
|
loading={isLoadingModels}
|
||||||
placeholder={isLoadingModels ? t('common.loading') : t('paintings.select_model')}>
|
placeholder={isLoadingModels ? t('common.loading') : t('paintings.select_model')}>
|
||||||
{Object.entries(modelOptions).map(([provider, models]) => {
|
{Object.entries(modelOptions).map(([provider, models]) => {
|
||||||
@ -861,11 +838,11 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('paintings.image.size')}</SettingTitle>
|
<SettingTitle className="mt-4 mb-1">{t('paintings.image.size')}</SettingTitle>
|
||||||
<Select
|
<Select
|
||||||
value={isCustomSize ? 'custom' : painting.image_size}
|
value={isCustomSize ? 'custom' : painting.image_size}
|
||||||
onChange={(value) => onSelectImageSize(value)}
|
onChange={(value) => onSelectImageSize(value)}
|
||||||
style={{ width: '100%' }}>
|
className="w-full">
|
||||||
{(() => {
|
{(() => {
|
||||||
const currentModel = allModels.find((m) => m.id === painting.model)
|
const currentModel = allModels.find((m) => m.id === painting.model)
|
||||||
const modelImageSizes = currentModel?.image_sizes || []
|
const modelImageSizes = currentModel?.image_sizes || []
|
||||||
@ -874,7 +851,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
return modelImageSizes.map((size) => {
|
return modelImageSizes.map((size) => {
|
||||||
return (
|
return (
|
||||||
<Select.Option key={size.value} value={size.value}>
|
<Select.Option key={size.value} value={size.value}>
|
||||||
<HStack style={{ alignItems: 'center', gap: 8 }}>
|
<HStack className="items-center gap-2">
|
||||||
<span>{size.label}</span>
|
<span>{size.label}</span>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
@ -884,7 +861,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
{/* 检查当前模型是否支持自定义尺寸 */}
|
{/* 检查当前模型是否支持自定义尺寸 */}
|
||||||
{allModels.find((m) => m.id === painting.model)?.is_custom_size && (
|
{allModels.find((m) => m.id === painting.model)?.is_custom_size && (
|
||||||
<Select.Option value="custom" key="custom">
|
<Select.Option value="custom" key="custom">
|
||||||
<HStack style={{ alignItems: 'center', gap: 8 }}>
|
<HStack className="items-center gap-2">
|
||||||
<span>{t('paintings.custom_size')}</span>
|
<span>{t('paintings.custom_size')}</span>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
@ -893,7 +870,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
{/* 自定义尺寸输入框 */}
|
{/* 自定义尺寸输入框 */}
|
||||||
{isCustomSize && allModels.find((m) => m.id === painting.model)?.is_custom_size && (
|
{isCustomSize && allModels.find((m) => m.id === painting.model)?.is_custom_size && (
|
||||||
<div style={{ marginTop: 10 }}>
|
<div className="mt-2.5">
|
||||||
<HStack style={{ gap: 8, alignItems: 'center' }}>
|
<HStack style={{ gap: 8, alignItems: 'center' }}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
placeholder="W"
|
placeholder="W"
|
||||||
@ -921,7 +898,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
{painting.generationMode === generationModeType.GENERATION && (
|
{painting.generationMode === generationModeType.GENERATION && (
|
||||||
<>
|
<>
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
<SettingTitle className="mt-4 mb-1">
|
||||||
{t('paintings.seed')}
|
{t('paintings.seed')}
|
||||||
<Tooltip title={t('paintings.seed_desc_tip')}>
|
<Tooltip title={t('paintings.seed_desc_tip')}>
|
||||||
<InfoIcon />
|
<InfoIcon />
|
||||||
@ -941,7 +918,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('paintings.style_type')}</SettingTitle>
|
<SettingTitle className="mt-4 mb-1">{t('paintings.style_type')}</SettingTitle>
|
||||||
<SliderContainer>
|
<SliderContainer>
|
||||||
<RadioTextBox>
|
<RadioTextBox>
|
||||||
{STYLE_TYPE_OPTIONS.map((ele) => (
|
{STYLE_TYPE_OPTIONS.map((ele) => (
|
||||||
@ -955,7 +932,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
</RadioTextBox>
|
</RadioTextBox>
|
||||||
</SliderContainer>
|
</SliderContainer>
|
||||||
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
<SettingTitle className="mt-4 mb-1">
|
||||||
{t('paintings.auto_create_paint')}
|
{t('paintings.auto_create_paint')}
|
||||||
<Tooltip title={t('paintings.auto_create_paint_tip')}>
|
<Tooltip title={t('paintings.auto_create_paint_tip')}>
|
||||||
<InfoIcon />
|
<InfoIcon />
|
||||||
@ -1032,11 +1009,6 @@ const ProviderTitleContainer = styled.div`
|
|||||||
const ProviderLogo = styled(Avatar)`
|
const ProviderLogo = styled(Avatar)`
|
||||||
border: 0.5px solid var(--color-border);
|
border: 0.5px solid var(--color-border);
|
||||||
`
|
`
|
||||||
const SelectOptionContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const ContentContainer = styled.div`
|
const ContentContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ 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 { getProviderLogo, isNewApiProvider, PROVIDER_URLS } 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'
|
||||||
@ -17,8 +17,7 @@ import {
|
|||||||
getPaintingsBackgroundOptionsLabel,
|
getPaintingsBackgroundOptionsLabel,
|
||||||
getPaintingsImageSizeOptionsLabel,
|
getPaintingsImageSizeOptionsLabel,
|
||||||
getPaintingsModerationOptionsLabel,
|
getPaintingsModerationOptionsLabel,
|
||||||
getPaintingsQualityOptionsLabel,
|
getPaintingsQualityOptionsLabel
|
||||||
getProviderLabel
|
|
||||||
} from '@renderer/i18n/label'
|
} from '@renderer/i18n/label'
|
||||||
import PaintingsList from '@renderer/pages/paintings/components/PaintingsList'
|
import PaintingsList from '@renderer/pages/paintings/components/PaintingsList'
|
||||||
import { DEFAULT_PAINTING, MODELS, SUPPORTED_MODELS } from '@renderer/pages/paintings/config/NewApiConfig'
|
import { DEFAULT_PAINTING, MODELS, SUPPORTED_MODELS } from '@renderer/pages/paintings/config/NewApiConfig'
|
||||||
@ -40,6 +39,7 @@ import styled from 'styled-components'
|
|||||||
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||||
import { SettingHelpLink, SettingTitle } from '../settings'
|
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||||
import Artboard from './components/Artboard'
|
import Artboard from './components/Artboard'
|
||||||
|
import ProviderSelect from './components/ProviderSelect'
|
||||||
import { checkProviderEnabled } from './utils'
|
import { checkProviderEnabled } from './utils'
|
||||||
|
|
||||||
const logger = loggerService.withContext('NewApiPage')
|
const logger = loggerService.withContext('NewApiPage')
|
||||||
@ -55,8 +55,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
}, [openai_image_generate, openai_image_edit])
|
}, [openai_image_generate, openai_image_edit])
|
||||||
|
|
||||||
const filteredPaintings = useMemo(() => newApiPaintings[mode] || [], [newApiPaintings, mode])
|
// moved below after newApiProvider is defined
|
||||||
const [painting, setPainting] = useState<PaintingAction>(filteredPaintings[0] || DEFAULT_PAINTING)
|
|
||||||
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||||
@ -67,27 +66,22 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const providers = useAllProviders()
|
const providers = useAllProviders()
|
||||||
const providerOptions = Options.map((option) => {
|
const location = useLocation()
|
||||||
const provider = providers.find((p) => p.id === option)
|
const routeName = location.pathname.split('/').pop() || 'new-api'
|
||||||
if (provider) {
|
const newApiProviders = providers.filter((p) => isNewApiProvider(p))
|
||||||
return {
|
|
||||||
label: getProviderLabel(provider.id),
|
|
||||||
value: provider.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
label: 'Unknown Provider',
|
|
||||||
value: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { generating } = useRuntime()
|
const { generating } = useRuntime()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
|
||||||
const { autoTranslateWithSpace } = useSettings()
|
const { autoTranslateWithSpace } = useSettings()
|
||||||
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||||
const newApiProvider = providers.find((p) => p.id === 'new-api')!
|
const newApiProvider = newApiProviders.find((p) => p.id === routeName) || newApiProviders[0]
|
||||||
|
|
||||||
|
const filteredPaintings = useMemo(
|
||||||
|
() => (newApiPaintings[mode] || []).filter((p) => p.providerId === newApiProvider.id),
|
||||||
|
[newApiPaintings, mode, newApiProvider.id]
|
||||||
|
)
|
||||||
|
const [painting, setPainting] = useState<PaintingAction>({ ...DEFAULT_PAINTING, providerId: newApiProvider.id })
|
||||||
|
|
||||||
const modeOptions = [
|
const modeOptions = [
|
||||||
{ label: t('paintings.mode.generate'), value: 'openai_image_generate' },
|
{ label: t('paintings.mode.generate'), value: 'openai_image_generate' },
|
||||||
@ -102,7 +96,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}, [editImageFiles])
|
}, [editImageFiles])
|
||||||
|
|
||||||
const updatePaintingState = (updates: Partial<PaintingAction>) => {
|
const updatePaintingState = (updates: Partial<PaintingAction>) => {
|
||||||
const updatedPainting = { ...painting, ...updates }
|
const updatedPainting = { ...painting, providerId: newApiProvider.id, ...updates }
|
||||||
setPainting(updatedPainting)
|
setPainting(updatedPainting)
|
||||||
updatePainting(mode, updatedPainting)
|
updatePainting(mode, updatedPainting)
|
||||||
}
|
}
|
||||||
@ -138,9 +132,10 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
return {
|
return {
|
||||||
...DEFAULT_PAINTING,
|
...DEFAULT_PAINTING,
|
||||||
model: painting.model || modelOptions[0]?.value || '',
|
model: painting.model || modelOptions[0]?.value || '',
|
||||||
id: uuid()
|
id: uuid(),
|
||||||
|
providerId: newApiProvider.id
|
||||||
}
|
}
|
||||||
}, [modelOptions, painting.model])
|
}, [modelOptions, painting.model, newApiProvider.id])
|
||||||
|
|
||||||
const selectedModelConfig = useMemo(
|
const selectedModelConfig = useMemo(
|
||||||
() => MODELS.find((m) => m.name === painting.model) || MODELS[0],
|
() => MODELS.find((m) => m.name === painting.model) || MODELS[0],
|
||||||
@ -444,11 +439,10 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
// 处理模式切换
|
// 处理模式切换
|
||||||
const handleModeChange = (value: string) => {
|
const handleModeChange = (value: string) => {
|
||||||
setMode(value as keyof PaintingsState)
|
setMode(value as keyof PaintingsState)
|
||||||
if (newApiPaintings[value as keyof PaintingsState] && newApiPaintings[value as keyof PaintingsState].length > 0) {
|
const list = (newApiPaintings[value as keyof PaintingsState] || []).filter(
|
||||||
setPainting(newApiPaintings[value as keyof PaintingsState][0])
|
(p) => p.providerId === newApiProvider.id
|
||||||
} else {
|
)
|
||||||
setPainting(DEFAULT_PAINTING)
|
setPainting(list[0] || { ...DEFAULT_PAINTING, providerId: newApiProvider.id })
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染配置项的函数
|
// 渲染配置项的函数
|
||||||
@ -473,8 +467,10 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const newPainting = getNewPainting()
|
const newPainting = getNewPainting()
|
||||||
addPainting(mode, newPainting)
|
addPainting(mode, newPainting)
|
||||||
setPainting(newPainting)
|
setPainting(newPainting)
|
||||||
|
} else {
|
||||||
|
setPainting(filteredPaintings[0])
|
||||||
}
|
}
|
||||||
}, [filteredPaintings, mode, addPainting, painting, getNewPainting])
|
}, [filteredPaintings, mode, addPainting, getNewPainting])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = spaceClickTimer.current
|
const timer = spaceClickTimer.current
|
||||||
@ -501,7 +497,9 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
<LeftContainer>
|
<LeftContainer>
|
||||||
<ProviderTitleContainer>
|
<ProviderTitleContainer>
|
||||||
<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={PROVIDER_URLS[newApiProvider.id]?.websites?.docs || 'https://docs.newapi.pro/apps/cherry-studio/'}>
|
||||||
{t('paintings.learn_more')}
|
{t('paintings.learn_more')}
|
||||||
<ProviderLogo
|
<ProviderLogo
|
||||||
shape="square"
|
shape="square"
|
||||||
@ -512,19 +510,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
</SettingHelpLink>
|
</SettingHelpLink>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
|
|
||||||
<Select
|
<ProviderSelect provider={newApiProvider} options={Options} onChange={handleProviderChange} />
|
||||||
value={providerOptions.find((p) => p.value === 'new-api')?.value}
|
|
||||||
onChange={handleProviderChange}
|
|
||||||
style={{ width: '100%' }}>
|
|
||||||
{providerOptions.map((provider) => (
|
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
|
||||||
<SelectOptionContainer>
|
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
|
||||||
{provider.label}
|
|
||||||
</SelectOptionContainer>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
{/* 当没有可用的 Image Generation 模型时,提示用户先去新增 */}
|
{/* 当没有可用的 Image Generation 模型时,提示用户先去新增 */}
|
||||||
{modelOptions.length === 0 && (
|
{modelOptions.length === 0 && (
|
||||||
@ -792,20 +778,12 @@ const ProviderLogo = styled(Avatar)`
|
|||||||
border: 0.5px solid var(--color-border);
|
border: 0.5px solid var(--color-border);
|
||||||
`
|
`
|
||||||
|
|
||||||
// 添加新的样式组件
|
|
||||||
const ModeSegmentedContainer = styled.div`
|
const ModeSegmentedContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SelectOptionContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
`
|
|
||||||
|
|
||||||
// 添加新的样式组件
|
|
||||||
const ProviderTitleContainer = styled.div`
|
const ProviderTitleContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { isNewApiProvider } from '@renderer/config/providers'
|
||||||
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setDefaultPaintingProvider } from '@renderer/store/settings'
|
import { setDefaultPaintingProvider } from '@renderer/store/settings'
|
||||||
import { PaintingProvider } from '@renderer/types'
|
import { PaintingProvider, SystemProviderId } from '@renderer/types'
|
||||||
import { FC, useEffect } from 'react'
|
import { FC, useEffect, useMemo } from 'react'
|
||||||
import { Route, Routes, useParams } from 'react-router-dom'
|
import { Route, Routes, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import AihubmixPage from './AihubmixPage'
|
import AihubmixPage from './AihubmixPage'
|
||||||
@ -14,19 +16,23 @@ import ZhipuPage from './ZhipuPage'
|
|||||||
|
|
||||||
const logger = loggerService.withContext('PaintingsRoutePage')
|
const logger = loggerService.withContext('PaintingsRoutePage')
|
||||||
|
|
||||||
const Options = ['zhipu', 'aihubmix', 'silicon', 'dmxapi', 'tokenflux', 'new-api']
|
const BASE_OPTIONS: SystemProviderId[] = ['zhipu', 'aihubmix', 'silicon', 'dmxapi', 'tokenflux']
|
||||||
|
|
||||||
const PaintingsRoutePage: FC = () => {
|
const PaintingsRoutePage: FC = () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const provider = params['*']
|
const provider = params['*']
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
const providers = useAllProviders()
|
||||||
|
const Options = useMemo(() => {
|
||||||
|
return [...BASE_OPTIONS, ...providers.filter((p) => isNewApiProvider(p)).map((p) => p.id)]
|
||||||
|
}, [providers])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
logger.debug(`defaultPaintingProvider: ${provider}`)
|
logger.debug(`defaultPaintingProvider: ${provider}`)
|
||||||
if (provider && Options.includes(provider)) {
|
if (provider && Options.includes(provider)) {
|
||||||
dispatch(setDefaultPaintingProvider(provider as PaintingProvider))
|
dispatch(setDefaultPaintingProvider(provider as PaintingProvider))
|
||||||
}
|
}
|
||||||
}, [provider, dispatch])
|
}, [provider, dispatch, Options])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
@ -36,7 +42,12 @@ const PaintingsRoutePage: FC = () => {
|
|||||||
<Route path="/silicon" element={<SiliconPage Options={Options} />} />
|
<Route path="/silicon" element={<SiliconPage Options={Options} />} />
|
||||||
<Route path="/dmxapi" element={<DmxapiPage Options={Options} />} />
|
<Route path="/dmxapi" element={<DmxapiPage Options={Options} />} />
|
||||||
<Route path="/tokenflux" element={<TokenFluxPage Options={Options} />} />
|
<Route path="/tokenflux" element={<TokenFluxPage Options={Options} />} />
|
||||||
<Route path="/new-api" element={<NewApiPage Options={Options} />} />
|
{/* new-api family providers are mounted dynamically below */}
|
||||||
|
{providers
|
||||||
|
.filter((p) => isNewApiProvider(p))
|
||||||
|
.map((p) => (
|
||||||
|
<Route key={p.id} path={`/${p.id}`} element={<NewApiPage Options={Options} />} />
|
||||||
|
))}
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,14 +12,12 @@ import { HStack, VStack } 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 { 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 { getProviderByModel } from '@renderer/services/AssistantService'
|
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
@ -27,7 +25,7 @@ import { useAppDispatch } from '@renderer/store'
|
|||||||
import { setGenerating } from '@renderer/store/runtime'
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
import type { FileMetadata, Painting } from '@renderer/types'
|
import type { FileMetadata, Painting } from '@renderer/types'
|
||||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||||
import { Avatar, Button, Input, InputNumber, Radio, Select, Slider, Switch, Tooltip } from 'antd'
|
import { Button, Input, InputNumber, Radio, Select, Slider, 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 type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
@ -40,6 +38,7 @@ import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
|||||||
import { SettingTitle } from '../settings'
|
import { SettingTitle } from '../settings'
|
||||||
import Artboard from './components/Artboard'
|
import Artboard from './components/Artboard'
|
||||||
import PaintingsList from './components/PaintingsList'
|
import PaintingsList from './components/PaintingsList'
|
||||||
|
import ProviderSelect from './components/ProviderSelect'
|
||||||
import { checkProviderEnabled } from './utils'
|
import { checkProviderEnabled } from './utils'
|
||||||
|
|
||||||
export const TEXT_TO_IMAGES_MODELS = [
|
export const TEXT_TO_IMAGES_MODELS = [
|
||||||
@ -115,22 +114,8 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const [painting, setPainting] = useState<Painting>(siliconflow_paintings[0] || DEFAULT_PAINTING)
|
const [painting, setPainting] = useState<Painting>(siliconflow_paintings[0] || DEFAULT_PAINTING)
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const providers = useAllProviders()
|
const providers = useAllProviders()
|
||||||
const providerOptions = Options.map((option) => {
|
|
||||||
const provider = providers.find((p) => p.id === option)
|
|
||||||
if (provider) {
|
|
||||||
return {
|
|
||||||
label: getProviderLabel(provider.id),
|
|
||||||
value: provider.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
label: 'Unknown Provider',
|
|
||||||
value: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const siliconflowProvider = providers.find((p) => p.id === 'silicon')
|
const siliconFlowProvider = providers.find((p) => p.id === 'silicon')!
|
||||||
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@ -170,7 +155,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onGenerate = async () => {
|
const onGenerate = async () => {
|
||||||
await checkProviderEnabled(siliconflowProvider!, t)
|
await checkProviderEnabled(siliconFlowProvider!, t)
|
||||||
|
|
||||||
if (painting.files.length > 0) {
|
if (painting.files.length > 0) {
|
||||||
const confirmed = await window.modal.confirm({
|
const confirmed = await window.modal.confirm({
|
||||||
@ -389,17 +374,8 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
<ContentContainer id="content-container">
|
<ContentContainer id="content-container">
|
||||||
<LeftContainer>
|
<LeftContainer>
|
||||||
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
||||||
<Select value={providerOptions[2].value} onChange={handleProviderChange}>
|
<ProviderSelect provider={siliconFlowProvider} options={Options} onChange={handleProviderChange} />
|
||||||
{providerOptions.map((provider) => (
|
<SettingTitle className="mt-4 mb-1">{t('common.model')}</SettingTitle>
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
|
||||||
<SelectOptionContainer>
|
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
|
||||||
{provider.label}
|
|
||||||
</SelectOptionContainer>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('common.model')}</SettingTitle>
|
|
||||||
<Select value={painting.model} options={modelOptions} onChange={onSelectModel} />
|
<Select value={painting.model} options={modelOptions} onChange={onSelectModel} />
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('paintings.image.size')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('paintings.image.size')}</SettingTitle>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
@ -672,14 +648,4 @@ const StyledInputNumber = styled(InputNumber)`
|
|||||||
width: 70px;
|
width: 70px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SelectOptionContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
|
||||||
flex-shrink: 0;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default SiliconPage
|
export default SiliconPage
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
|
|||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
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 FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
@ -31,6 +30,7 @@ import { SettingHelpLink, SettingTitle } from '../settings'
|
|||||||
import Artboard from './components/Artboard'
|
import Artboard from './components/Artboard'
|
||||||
import { DynamicFormRender } from './components/DynamicFormRender'
|
import { DynamicFormRender } from './components/DynamicFormRender'
|
||||||
import PaintingsList from './components/PaintingsList'
|
import PaintingsList from './components/PaintingsList'
|
||||||
|
import ProviderSelect from './components/ProviderSelect'
|
||||||
import { DEFAULT_TOKENFLUX_PAINTING, type TokenFluxModel } from './config/tokenFluxConfig'
|
import { DEFAULT_TOKENFLUX_PAINTING, type TokenFluxModel } from './config/tokenFluxConfig'
|
||||||
import { checkProviderEnabled } from './utils'
|
import { checkProviderEnabled } from './utils'
|
||||||
import TokenFluxService from './utils/TokenFluxService'
|
import TokenFluxService from './utils/TokenFluxService'
|
||||||
@ -55,21 +55,6 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
tokenFluxPaintings[0] || { ...DEFAULT_TOKENFLUX_PAINTING, id: uuid() }
|
tokenFluxPaintings[0] || { ...DEFAULT_TOKENFLUX_PAINTING, id: uuid() }
|
||||||
)
|
)
|
||||||
|
|
||||||
const providerOptions = Options.map((option) => {
|
|
||||||
const provider = providers.find((p) => p.id === option)
|
|
||||||
if (provider) {
|
|
||||||
return {
|
|
||||||
label: getProviderLabel(provider.id),
|
|
||||||
value: provider.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
label: 'Unknown Provider',
|
|
||||||
value: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { generating } = useRuntime()
|
const { generating } = useRuntime()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -387,19 +372,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
</SettingHelpLink>
|
</SettingHelpLink>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
|
|
||||||
<Select
|
<ProviderSelect provider={tokenfluxProvider} options={Options} onChange={handleProviderChange} />
|
||||||
value={providerOptions.find((p) => p.value === 'tokenflux')?.value}
|
|
||||||
onChange={handleProviderChange}
|
|
||||||
style={{ width: '100%' }}>
|
|
||||||
{providerOptions.map((provider) => (
|
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
|
||||||
<SelectOptionContainer>
|
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
|
||||||
{provider.label}
|
|
||||||
</SelectOptionContainer>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
{/* Model & Pricing Section */}
|
{/* Model & Pricing Section */}
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
@ -775,11 +748,4 @@ const ProviderTitleContainer = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SelectOptionContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default TokenFluxPage
|
export default TokenFluxPage
|
||||||
|
|||||||
@ -8,7 +8,6 @@ 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 { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
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'
|
||||||
@ -24,6 +23,7 @@ import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
|||||||
import { SettingHelpLink, SettingTitle } from '../settings'
|
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||||
import Artboard from './components/Artboard'
|
import Artboard from './components/Artboard'
|
||||||
import PaintingsList from './components/PaintingsList'
|
import PaintingsList from './components/PaintingsList'
|
||||||
|
import ProviderSelect from './components/ProviderSelect'
|
||||||
import {
|
import {
|
||||||
COURSE_URL,
|
COURSE_URL,
|
||||||
DEFAULT_PAINTING,
|
DEFAULT_PAINTING,
|
||||||
@ -50,21 +50,6 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [painting?.id]) // 只在painting的id改变时执行,避免无限循环
|
}, [painting?.id]) // 只在painting的id改变时执行,避免无限循环
|
||||||
|
|
||||||
const providerOptions = Options.map((option) => {
|
|
||||||
const provider = providers.find((p) => p.id === option)
|
|
||||||
if (provider) {
|
|
||||||
return {
|
|
||||||
label: getProviderLabel(provider.id),
|
|
||||||
value: provider.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
label: 'Unknown Provider',
|
|
||||||
value: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const zhipuProvider = providers.find((p) => p.id === 'zhipu')!
|
const zhipuProvider = providers.find((p) => p.id === 'zhipu')!
|
||||||
|
|
||||||
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||||
@ -370,16 +355,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ProviderTitleContainer>
|
</ProviderTitleContainer>
|
||||||
<Select value={providerOptions[0].value} onChange={handleProviderChange} style={{ marginBottom: 15 }}>
|
<ProviderSelect provider={zhipuProvider} options={Options} onChange={handleProviderChange} className="mb-4" />
|
||||||
{providerOptions.map((provider) => (
|
|
||||||
<Select.Option value={provider.value} key={provider.value}>
|
|
||||||
<SelectOptionContainer>
|
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
|
||||||
{provider.label}
|
|
||||||
</SelectOptionContainer>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('common.model')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('common.model')}</SettingTitle>
|
||||||
<Select
|
<Select
|
||||||
@ -563,12 +539,6 @@ const ProviderTitleContainer = styled.div`
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SelectOptionContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
const ProviderLogo = styled(Avatar)`
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
`
|
`
|
||||||
|
|||||||
@ -0,0 +1,99 @@
|
|||||||
|
import { Select, SelectItem } from '@heroui/react'
|
||||||
|
import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar'
|
||||||
|
import { getProviderLogo } from '@renderer/config/providers'
|
||||||
|
import ImageStorage from '@renderer/services/ImageStorage'
|
||||||
|
import { getProviderNameById } from '@renderer/services/ProviderService'
|
||||||
|
import { Provider } from '@types'
|
||||||
|
import React, { FC, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
type ProviderSelectProps = {
|
||||||
|
provider: Provider
|
||||||
|
options: string[]
|
||||||
|
onChange: (value: string) => void
|
||||||
|
style?: React.CSSProperties
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProviderSelect: FC<ProviderSelectProps> = ({ provider, options, onChange, style, className }) => {
|
||||||
|
const [customLogos, setCustomLogos] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadLogos = async () => {
|
||||||
|
const logos: Record<string, string> = {}
|
||||||
|
for (const providerId of options) {
|
||||||
|
try {
|
||||||
|
const logoData = await ImageStorage.get(`provider-${providerId}`)
|
||||||
|
if (logoData) {
|
||||||
|
logos[providerId] = logoData
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors for providers without custom logos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCustomLogos(logos)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLogos()
|
||||||
|
}, [options])
|
||||||
|
|
||||||
|
const getProviderLogoSrc = (providerId: string) => {
|
||||||
|
const systemLogo = getProviderLogo(providerId)
|
||||||
|
if (systemLogo) {
|
||||||
|
return systemLogo
|
||||||
|
}
|
||||||
|
return customLogos[providerId]
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerOptions = options.map((option) => {
|
||||||
|
return {
|
||||||
|
label: getProviderNameById(option),
|
||||||
|
value: option
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
selectedKeys={[provider.id]}
|
||||||
|
onSelectionChange={(keys) => {
|
||||||
|
const selectedKey = Array.from(keys)[0] as string
|
||||||
|
onChange(selectedKey)
|
||||||
|
}}
|
||||||
|
style={style}
|
||||||
|
className={`w-full ${className || ''}`}
|
||||||
|
renderValue={(items) => {
|
||||||
|
return items.map((item) => (
|
||||||
|
<div key={item.key} className="flex items-center gap-2">
|
||||||
|
<div className="flex h-4 w-4 items-center justify-center">
|
||||||
|
<ProviderAvatarPrimitive
|
||||||
|
providerId={item.key as string}
|
||||||
|
providerName={item.textValue || ''}
|
||||||
|
logoSrc={getProviderLogoSrc(item.key as string)}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>{item.textValue}</span>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}}>
|
||||||
|
{providerOptions.map((providerOption) => (
|
||||||
|
<SelectItem
|
||||||
|
key={providerOption.value}
|
||||||
|
textValue={providerOption.label}
|
||||||
|
startContent={
|
||||||
|
<div className="flex h-4 w-4 items-center justify-center">
|
||||||
|
<ProviderAvatarPrimitive
|
||||||
|
providerId={providerOption.value}
|
||||||
|
providerName={providerOption.label}
|
||||||
|
logoSrc={getProviderLogoSrc(providerOption.value)}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
{providerOption.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProviderSelect
|
||||||
@ -252,7 +252,8 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
{ label: 'OpenAI-Response', value: 'openai-response' },
|
{ label: 'OpenAI-Response', value: 'openai-response' },
|
||||||
{ label: 'Gemini', value: 'gemini' },
|
{ label: 'Gemini', value: 'gemini' },
|
||||||
{ label: 'Anthropic', value: 'anthropic' },
|
{ label: 'Anthropic', value: 'anthropic' },
|
||||||
{ label: 'Azure OpenAI', value: 'azure-openai' }
|
{ label: 'Azure OpenAI', value: 'azure-openai' },
|
||||||
|
{ label: 'New API', value: 'new-api' }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -12,6 +12,15 @@ export function getProviderName(model?: Model) {
|
|||||||
return getFancyProviderName(provider)
|
return getFancyProviderName(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProviderNameById(pid: string) {
|
||||||
|
const provider = getStoreProviders().find((p) => p.id === pid)
|
||||||
|
if (provider) {
|
||||||
|
return getFancyProviderName(provider)
|
||||||
|
} else {
|
||||||
|
return 'Unknown Provider'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getProviderByModel(model?: Model) {
|
export function getProviderByModel(model?: Model) {
|
||||||
const id = model?.provider
|
const id = model?.provider
|
||||||
const provider = getStoreProviders().find((p) => p.id === id)
|
const provider = getStoreProviders().find((p) => p.id === id)
|
||||||
|
|||||||
@ -370,6 +370,7 @@ export type ProviderType =
|
|||||||
| 'mistral'
|
| 'mistral'
|
||||||
| 'aws-bedrock'
|
| 'aws-bedrock'
|
||||||
| 'vertex-anthropic'
|
| 'vertex-anthropic'
|
||||||
|
| 'new-api'
|
||||||
|
|
||||||
export type ModelType = 'text' | 'vision' | 'embedding' | 'reasoning' | 'function_calling' | 'web_search' | 'rerank'
|
export type ModelType = 'text' | 'vision' | 'embedding' | 'reasoning' | 'function_calling' | 'web_search' | 'rerank'
|
||||||
|
|
||||||
@ -418,6 +419,8 @@ export type PaintingParams = {
|
|||||||
id: string
|
id: string
|
||||||
urls: string[]
|
urls: string[]
|
||||||
files: FileMetadata[]
|
files: FileMetadata[]
|
||||||
|
// provider that this painting belongs to (for new-api family separation)
|
||||||
|
providerId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaintingProvider = 'zhipu' | 'aihubmix' | 'silicon' | 'dmxapi' | 'new-api'
|
export type PaintingProvider = 'zhipu' | 'aihubmix' | 'silicon' | 'dmxapi' | 'new-api'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user