feat: add provider type

This commit is contained in:
kangfenmao 2024-11-18 13:04:46 +08:00
parent c33c0b20f2
commit 1b8a3885f7
12 changed files with 109 additions and 36 deletions

View File

@ -11,8 +11,12 @@ import FireworksProviderLogo from '@renderer/assets/images/providers/fireworks.p
import GithubProviderLogo from '@renderer/assets/images/providers/github.png' import GithubProviderLogo from '@renderer/assets/images/providers/github.png'
import GoogleProviderLogo from '@renderer/assets/images/providers/google.png' import GoogleProviderLogo from '@renderer/assets/images/providers/google.png'
import GraphRagProviderLogo from '@renderer/assets/images/providers/graph-rag.png' import GraphRagProviderLogo from '@renderer/assets/images/providers/graph-rag.png'
import GrokProviderLogo from '@renderer/assets/images/providers/grok.png'
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png' import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
import HyperbolicProviderLogo from '@renderer/assets/images/providers/hyperbolic.png'
import JinaProviderLogo from '@renderer/assets/images/providers/jina.png'
import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png' import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png'
import MistralProviderLogo from '@renderer/assets/images/providers/mistral.png'
import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.png' import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.png'
import NvidiaProviderLogo from '@renderer/assets/images/providers/nvidia.png' import NvidiaProviderLogo from '@renderer/assets/images/providers/nvidia.png'
import OcoolAiProviderLogo from '@renderer/assets/images/providers/ocoolai.png' import OcoolAiProviderLogo from '@renderer/assets/images/providers/ocoolai.png'
@ -24,10 +28,6 @@ import StepProviderLogo from '@renderer/assets/images/providers/step.png'
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png' import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
import ZeroOneProviderLogo from '@renderer/assets/images/providers/zero-one.png' import ZeroOneProviderLogo from '@renderer/assets/images/providers/zero-one.png'
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png' import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
import GrokProviderLogo from '@renderer/assets/images/providers/grok.png'
import HyperbolicProviderLogo from '@renderer/assets/images/providers/hyperbolic.png'
import MistralProviderLogo from '@renderer/assets/images/providers/mistral.png'
import JinaProviderLogo from '@renderer/assets/images/providers/jina.png'
export function getProviderLogo(providerId: string) { export function getProviderLogo(providerId: string) {
switch (providerId) { switch (providerId) {
@ -326,6 +326,7 @@ export const PROVIDER_CONFIG = {
}, },
websites: { websites: {
official: 'https://app.hyperbolic.xyz', official: 'https://app.hyperbolic.xyz',
apiKey: 'https://app.hyperbolic.xyz/settings',
docs: 'https://docs.hyperbolic.xyz', docs: 'https://docs.hyperbolic.xyz',
models: 'https://app.hyperbolic.xyz/models' models: 'https://app.hyperbolic.xyz/models'
} }
@ -336,6 +337,7 @@ export const PROVIDER_CONFIG = {
}, },
websites: { websites: {
official: 'https://mistral.ai', official: 'https://mistral.ai',
apiKey: 'https://console.mistral.ai/api-keys/',
docs: 'https://docs.mistral.ai', docs: 'https://docs.mistral.ai',
models: 'https://docs.mistral.ai/getting-started/models/models_overview' models: 'https://docs.mistral.ai/getting-started/models/models_overview'
} }
@ -346,6 +348,7 @@ export const PROVIDER_CONFIG = {
}, },
websites: { websites: {
official: 'https://jina.ai', official: 'https://jina.ai',
apiKey: 'https://jina.ai/',
docs: 'https://jina.ai', docs: 'https://jina.ai',
models: 'https://jina.ai' models: 'https://jina.ai'
} }

View File

@ -377,8 +377,10 @@
"not_checked": "Not Checked", "not_checked": "Not Checked",
"delete.title": "Delete Provider", "delete.title": "Delete Provider",
"delete.content": "Are you sure you want to delete this provider?", "delete.content": "Are you sure you want to delete this provider?",
"edit.name": "Provider Name", "add.title": "Add Provider",
"edit.name.placeholder": "Example: OpenAI", "add.name": "Provider Name",
"add.name.placeholder": "Example: OpenAI",
"add.type": "Provider Type",
"no_models": "Please add models first before checking the API connection" "no_models": "Please add models first before checking the API connection"
} }
}, },

View File

@ -377,8 +377,10 @@
"not_checked": "Не проверено", "not_checked": "Не проверено",
"delete.title": "Удалить провайдер", "delete.title": "Удалить провайдер",
"delete.content": "Вы уверены, что хотите удалить этот провайдер?", "delete.content": "Вы уверены, что хотите удалить этот провайдер?",
"edit.name": "Имя провайдера", "add.title": "Добавить провайдер",
"edit.name.placeholder": "Пример: OpenAI", "add.name": "Имя провайдера",
"add.name.placeholder": "Пример: OpenAI",
"add.type": "Тип провайдера",
"no_models": "Пожалуйста, добавьте модели перед проверкой соединения с API" "no_models": "Пожалуйста, добавьте модели перед проверкой соединения с API"
} }
}, },

View File

@ -365,8 +365,10 @@
"not_checked": "未检查", "not_checked": "未检查",
"delete.title": "删除提供商", "delete.title": "删除提供商",
"delete.content": "确定要删除此模型提供商吗?", "delete.content": "确定要删除此模型提供商吗?",
"edit.name": "模型提供商名称", "add.title": "添加提供商",
"edit.name.placeholder": "例如 OpenAI", "add.name": "提供商名称",
"add.name.placeholder": "例如 OpenAI",
"add.type": "提供商类型",
"no_models": "请先添加模型再检查 API 连接" "no_models": "请先添加模型再检查 API 连接"
} }
}, },

View File

@ -364,9 +364,11 @@
"remove_duplicate_keys": "移除重複密鑰", "remove_duplicate_keys": "移除重複密鑰",
"not_checked": "未檢查", "not_checked": "未檢查",
"delete.title": "刪除提供者", "delete.title": "刪除提供者",
".delete.content": "確定要刪除此提供者嗎?", "delete.content": "確定要刪除此提供者嗎?",
"edit.name": "提供者名稱", "add.title": "添加提供者",
"edit.name.placeholder": "例如OpenAI", "add.name": "提供者名稱",
"add.name.placeholder": "例如OpenAI",
"add.type": "提供商類型",
"no_models": "請先添加模型再檢查 API 連接" "no_models": "請先添加模型再檢查 API 連接"
} }
}, },

View File

@ -1,31 +1,32 @@
import { TopView } from '@renderer/components/TopView' import { TopView } from '@renderer/components/TopView'
import { Provider } from '@renderer/types' import { Provider, ProviderType } from '@renderer/types'
import { Input, Modal } from 'antd' import { Divider, Form, Input, Modal, Select } from 'antd'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
interface Props { interface Props {
provider?: Provider provider?: Provider
resolve: (name: string) => void resolve: (result: { name: string; type: ProviderType }) => void
} }
const PopupContainer: React.FC<Props> = ({ provider, resolve }) => { const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
const [open, setOpen] = useState(true) const [open, setOpen] = useState(true)
const [name, setName] = useState(provider?.name || '') const [name, setName] = useState(provider?.name || '')
const [type, setType] = useState<ProviderType>(provider?.type || 'openai')
const { t } = useTranslation() const { t } = useTranslation()
const onOk = () => { const onOk = () => {
setOpen(false) setOpen(false)
resolve(name) resolve({ name, type })
} }
const onCancel = () => { const onCancel = () => {
setOpen(false) setOpen(false)
resolve('') resolve({ name: '', type: 'openai' })
} }
const onClose = () => { const onClose = () => {
resolve(name) resolve({ name, type })
} }
const buttonDisabled = name.length === 0 const buttonDisabled = name.length === 0
@ -39,15 +40,31 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
width={360} width={360}
closable={false} closable={false}
centered centered
title={t('settings.provider.edit.name')} title={t('settings.provider.add.title')}
okButtonProps={{ disabled: buttonDisabled }}> okButtonProps={{ disabled: buttonDisabled }}>
<Input <Divider style={{ margin: '8px 0' }} />
value={name} <Form layout="vertical" style={{ gap: 8 }}>
onChange={(e) => setName(e.target.value.trim())} <Form.Item label={t('settings.provider.add.name')} style={{ marginBottom: 8 }}>
placeholder={t('settings.provider.edit.name.placeholder')} <Input
onKeyDown={(e) => e.key === 'Enter' && onOk()} value={name}
maxLength={32} onChange={(e) => setName(e.target.value.trim())}
/> placeholder={t('settings.provider.add.name.placeholder')}
onKeyDown={(e) => e.key === 'Enter' && onOk()}
maxLength={32}
/>
</Form.Item>
<Form.Item label={t('settings.provider.add.type')} style={{ marginBottom: 0 }}>
<Select
value={type}
onChange={setType}
options={[
{ label: 'OpenAI', value: 'openai' },
{ label: 'Gemini', value: 'gemini' },
{ label: 'Anthropic', value: 'anthropic' }
]}
/>
</Form.Item>
</Form>
</Modal> </Modal>
) )
} }
@ -58,7 +75,7 @@ export default class AddProviderPopup {
TopView.hide('AddProviderPopup') TopView.hide('AddProviderPopup')
} }
static show(provider?: Provider) { static show(provider?: Provider) {
return new Promise<string>((resolve) => { return new Promise<{ name: string; type: ProviderType }>((resolve) => {
TopView.show( TopView.show(
<PopupContainer <PopupContainer
provider={provider} provider={provider}

View File

@ -31,15 +31,16 @@ const ProvidersList: FC = () => {
} }
const onAddProvider = async () => { const onAddProvider = async () => {
const prividerName = await AddProviderPopup.show() const { name: prividerName, type } = await AddProviderPopup.show()
if (!prividerName) { if (!prividerName.trim()) {
return return
} }
const provider = { const provider = {
id: uuid(), id: uuid(),
name: prividerName, name: prividerName.trim(),
type,
apiKey: '', apiKey: '',
apiHost: '', apiHost: '',
models: [], models: [],
@ -58,8 +59,8 @@ const ProvidersList: FC = () => {
key: 'edit', key: 'edit',
icon: <EditOutlined />, icon: <EditOutlined />,
async onClick() { async onClick() {
const name = await AddProviderPopup.show(provider) const { name, type } = await AddProviderPopup.show(provider)
name && updateProvider({ ...provider, name }) name && updateProvider({ ...provider, name, type })
} }
}, },
{ {

View File

@ -7,7 +7,7 @@ import OpenAIProvider from './OpenAIProvider'
export default class ProviderFactory { export default class ProviderFactory {
static create(provider: Provider): BaseProvider { static create(provider: Provider): BaseProvider {
switch (provider.id) { switch (provider.type) {
case 'anthropic': case 'anthropic':
return new AnthropicProvider(provider) return new AnthropicProvider(provider)
case 'gemini': case 'gemini':
@ -19,5 +19,5 @@ export default class ProviderFactory {
} }
export function isOpenAIProvider(provider: Provider) { export function isOpenAIProvider(provider: Provider) {
return !['anthropic', 'gemini'].includes(provider.id) return !['anthropic', 'gemini'].includes(provider.type)
} }

View File

@ -24,7 +24,7 @@ const persistedReducer = persistReducer(
{ {
key: 'cherry-studio', key: 'cherry-studio',
storage, storage,
version: 40, version: 41,
blacklist: ['runtime'], blacklist: ['runtime'],
migrate migrate
}, },

View File

@ -26,6 +26,7 @@ const initialState: LlmState = {
{ {
id: 'silicon', id: 'silicon',
name: 'Silicon', name: 'Silicon',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.siliconflow.cn', apiHost: 'https://api.siliconflow.cn',
models: SYSTEM_MODELS.silicon, models: SYSTEM_MODELS.silicon,
@ -35,6 +36,7 @@ const initialState: LlmState = {
{ {
id: 'ollama', id: 'ollama',
name: 'Ollama', name: 'Ollama',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'http://localhost:11434/v1/', apiHost: 'http://localhost:11434/v1/',
models: SYSTEM_MODELS.ollama, models: SYSTEM_MODELS.ollama,
@ -44,6 +46,7 @@ const initialState: LlmState = {
{ {
id: 'anthropic', id: 'anthropic',
name: 'Anthropic', name: 'Anthropic',
type: 'anthropic',
apiKey: '', apiKey: '',
apiHost: 'https://api.anthropic.com/', apiHost: 'https://api.anthropic.com/',
models: SYSTEM_MODELS.anthropic, models: SYSTEM_MODELS.anthropic,
@ -53,6 +56,7 @@ const initialState: LlmState = {
{ {
id: 'openai', id: 'openai',
name: 'OpenAI', name: 'OpenAI',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.openai.com', apiHost: 'https://api.openai.com',
models: SYSTEM_MODELS.openai, models: SYSTEM_MODELS.openai,
@ -62,6 +66,7 @@ const initialState: LlmState = {
{ {
id: 'azure-openai', id: 'azure-openai',
name: 'Azure OpenAI', name: 'Azure OpenAI',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: '', apiHost: '',
apiVersion: '', apiVersion: '',
@ -72,6 +77,7 @@ const initialState: LlmState = {
{ {
id: 'gemini', id: 'gemini',
name: 'Gemini', name: 'Gemini',
type: 'gemini',
apiKey: '', apiKey: '',
apiHost: 'https://generativelanguage.googleapis.com', apiHost: 'https://generativelanguage.googleapis.com',
models: SYSTEM_MODELS.gemini, models: SYSTEM_MODELS.gemini,
@ -81,6 +87,7 @@ const initialState: LlmState = {
{ {
id: 'deepseek', id: 'deepseek',
name: 'deepseek', name: 'deepseek',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.deepseek.com', apiHost: 'https://api.deepseek.com',
models: SYSTEM_MODELS.deepseek, models: SYSTEM_MODELS.deepseek,
@ -90,6 +97,7 @@ const initialState: LlmState = {
{ {
id: 'ocoolai', id: 'ocoolai',
name: 'ocoolAI', name: 'ocoolAI',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://one.ooo.cool', apiHost: 'https://one.ooo.cool',
models: SYSTEM_MODELS.ocoolai, models: SYSTEM_MODELS.ocoolai,
@ -99,6 +107,7 @@ const initialState: LlmState = {
{ {
id: 'github', id: 'github',
name: 'Github Models', name: 'Github Models',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://models.inference.ai.azure.com/', apiHost: 'https://models.inference.ai.azure.com/',
models: SYSTEM_MODELS.github, models: SYSTEM_MODELS.github,
@ -108,6 +117,7 @@ const initialState: LlmState = {
{ {
id: 'yi', id: 'yi',
name: 'Yi', name: 'Yi',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.lingyiwanwu.com', apiHost: 'https://api.lingyiwanwu.com',
models: SYSTEM_MODELS.yi, models: SYSTEM_MODELS.yi,
@ -117,6 +127,7 @@ const initialState: LlmState = {
{ {
id: 'zhipu', id: 'zhipu',
name: 'ZhiPu', name: 'ZhiPu',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://open.bigmodel.cn/api/paas/v4/', apiHost: 'https://open.bigmodel.cn/api/paas/v4/',
models: SYSTEM_MODELS.zhipu, models: SYSTEM_MODELS.zhipu,
@ -126,6 +137,7 @@ const initialState: LlmState = {
{ {
id: 'moonshot', id: 'moonshot',
name: 'Moonshot AI', name: 'Moonshot AI',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.moonshot.cn', apiHost: 'https://api.moonshot.cn',
models: SYSTEM_MODELS.moonshot, models: SYSTEM_MODELS.moonshot,
@ -135,6 +147,7 @@ const initialState: LlmState = {
{ {
id: 'baichuan', id: 'baichuan',
name: 'BAICHUAN AI', name: 'BAICHUAN AI',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.baichuan-ai.com', apiHost: 'https://api.baichuan-ai.com',
models: SYSTEM_MODELS.baichuan, models: SYSTEM_MODELS.baichuan,
@ -144,6 +157,7 @@ const initialState: LlmState = {
{ {
id: 'dashscope', id: 'dashscope',
name: 'Bailian', name: 'Bailian',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://dashscope.aliyuncs.com/compatible-mode/v1/', apiHost: 'https://dashscope.aliyuncs.com/compatible-mode/v1/',
models: SYSTEM_MODELS.bailian, models: SYSTEM_MODELS.bailian,
@ -153,6 +167,7 @@ const initialState: LlmState = {
{ {
id: 'stepfun', id: 'stepfun',
name: 'StepFun', name: 'StepFun',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.stepfun.com', apiHost: 'https://api.stepfun.com',
models: SYSTEM_MODELS.stepfun, models: SYSTEM_MODELS.stepfun,
@ -162,6 +177,7 @@ const initialState: LlmState = {
{ {
id: 'doubao', id: 'doubao',
name: 'doubao', name: 'doubao',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://ark.cn-beijing.volces.com/api/v3/', apiHost: 'https://ark.cn-beijing.volces.com/api/v3/',
models: SYSTEM_MODELS.doubao, models: SYSTEM_MODELS.doubao,
@ -171,6 +187,7 @@ const initialState: LlmState = {
{ {
id: 'minimax', id: 'minimax',
name: 'MiniMax', name: 'MiniMax',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.minimax.chat/v1/', apiHost: 'https://api.minimax.chat/v1/',
models: SYSTEM_MODELS.minimax, models: SYSTEM_MODELS.minimax,
@ -180,6 +197,7 @@ const initialState: LlmState = {
{ {
id: 'graphrag-kylin-mountain', id: 'graphrag-kylin-mountain',
name: 'GraphRAG', name: 'GraphRAG',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: '', apiHost: '',
models: [], models: [],
@ -189,6 +207,7 @@ const initialState: LlmState = {
{ {
id: 'openrouter', id: 'openrouter',
name: 'OpenRouter', name: 'OpenRouter',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://openrouter.ai/api/v1/', apiHost: 'https://openrouter.ai/api/v1/',
models: SYSTEM_MODELS.openrouter, models: SYSTEM_MODELS.openrouter,
@ -198,6 +217,7 @@ const initialState: LlmState = {
{ {
id: 'groq', id: 'groq',
name: 'Groq', name: 'Groq',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.groq.com/openai', apiHost: 'https://api.groq.com/openai',
models: SYSTEM_MODELS.groq, models: SYSTEM_MODELS.groq,
@ -207,6 +227,7 @@ const initialState: LlmState = {
{ {
id: 'together', id: 'together',
name: 'Together', name: 'Together',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.together.xyz', apiHost: 'https://api.together.xyz',
models: SYSTEM_MODELS.together, models: SYSTEM_MODELS.together,
@ -216,6 +237,7 @@ const initialState: LlmState = {
{ {
id: 'fireworks', id: 'fireworks',
name: 'Fireworks', name: 'Fireworks',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.fireworks.ai/inference', apiHost: 'https://api.fireworks.ai/inference',
models: SYSTEM_MODELS.fireworks, models: SYSTEM_MODELS.fireworks,
@ -225,6 +247,7 @@ const initialState: LlmState = {
{ {
id: 'zhinao', id: 'zhinao',
name: 'zhinao', name: 'zhinao',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.360.cn', apiHost: 'https://api.360.cn',
models: SYSTEM_MODELS.zhinao, models: SYSTEM_MODELS.zhinao,
@ -234,6 +257,7 @@ const initialState: LlmState = {
{ {
id: 'hunyuan', id: 'hunyuan',
name: 'hunyuan', name: 'hunyuan',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.hunyuan.cloud.tencent.com', apiHost: 'https://api.hunyuan.cloud.tencent.com',
models: SYSTEM_MODELS.hunyuan, models: SYSTEM_MODELS.hunyuan,
@ -243,6 +267,7 @@ const initialState: LlmState = {
{ {
id: 'nvidia', id: 'nvidia',
name: 'nvidia', name: 'nvidia',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://integrate.api.nvidia.com', apiHost: 'https://integrate.api.nvidia.com',
models: SYSTEM_MODELS.nvidia, models: SYSTEM_MODELS.nvidia,
@ -252,6 +277,7 @@ const initialState: LlmState = {
{ {
id: 'grok', id: 'grok',
name: 'Grok', name: 'Grok',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.x.ai', apiHost: 'https://api.x.ai',
models: SYSTEM_MODELS.grok, models: SYSTEM_MODELS.grok,
@ -261,6 +287,7 @@ const initialState: LlmState = {
{ {
id: 'hyperbolic', id: 'hyperbolic',
name: 'Hyperbolic', name: 'Hyperbolic',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.hyperbolic.xyz', apiHost: 'https://api.hyperbolic.xyz',
models: SYSTEM_MODELS.hyperbolic, models: SYSTEM_MODELS.hyperbolic,
@ -270,6 +297,7 @@ const initialState: LlmState = {
{ {
id: 'mistral', id: 'mistral',
name: 'Mistral', name: 'Mistral',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://api.mistral.ai', apiHost: 'https://api.mistral.ai',
models: SYSTEM_MODELS.mistral, models: SYSTEM_MODELS.mistral,
@ -288,6 +316,7 @@ const initialState: LlmState = {
{ {
id: 'aihubmix', id: 'aihubmix',
name: 'AiHubMix', name: 'AiHubMix',
type: 'openai',
apiKey: '', apiKey: '',
apiHost: 'https://aihubmix.com', apiHost: 'https://aihubmix.com',
models: SYSTEM_MODELS.aihubmix, models: SYSTEM_MODELS.aihubmix,

View File

@ -677,6 +677,18 @@ const migrateConfig = {
'40': (state: RootState) => { '40': (state: RootState) => {
state.settings.tray = true state.settings.tray = true
return state return state
},
'41': (state: RootState) => {
state.llm.providers.forEach((provider) => {
if (provider.id === 'gemini') {
provider.type = 'gemini'
} else if (provider.id === 'anthropic') {
provider.type = 'anthropic'
} else {
provider.type = 'openai'
}
})
return state
} }
} }

View File

@ -66,6 +66,7 @@ export type User = {
export type Provider = { export type Provider = {
id: string id: string
type: ProviderType
name: string name: string
apiKey: string apiKey: string
apiHost: string apiHost: string
@ -75,6 +76,8 @@ export type Provider = {
isSystem?: boolean isSystem?: boolean
} }
export type ProviderType = 'openai' | 'anthropic' | 'gemini'
export type Model = { export type Model = {
id: string id: string
provider: string provider: string