diff --git a/src/renderer/src/assets/images/providers/gpustack.svg b/src/renderer/src/assets/images/providers/gpustack.svg
new file mode 100644
index 00000000..95a07f91
--- /dev/null
+++ b/src/renderer/src/assets/images/providers/gpustack.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts
index 4292b09c..04db317b 100644
--- a/src/renderer/src/config/models.ts
+++ b/src/renderer/src/config/models.ts
@@ -1780,7 +1780,8 @@ export const SYSTEM_MODELS: Record = {
name: 'DeepSeek V3',
group: 'DeepSeek'
}
- ]
+ ],
+ gpustack: []
}
export const TEXT_TO_IMAGES_MODELS = [
diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts
index f7936395..485ea7e2 100644
--- a/src/renderer/src/config/providers.ts
+++ b/src/renderer/src/config/providers.ts
@@ -12,6 +12,7 @@ import FireworksProviderLogo from '@renderer/assets/images/providers/fireworks.p
import GiteeAIProviderLogo from '@renderer/assets/images/providers/gitee-ai.png'
import GithubProviderLogo from '@renderer/assets/images/providers/github.png'
import GoogleProviderLogo from '@renderer/assets/images/providers/google.png'
+import GPUStackProviderLogo from '@renderer/assets/images/providers/gpustack.svg'
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'
@@ -123,6 +124,8 @@ export function getProviderLogo(providerId: string) {
return O3ProviderLogo
case 'tencent-cloud-ti':
return TencentCloudProviderLogo
+ case 'gpustack':
+ return GPUStackProviderLogo
default:
return undefined
}
@@ -572,5 +575,15 @@ export const PROVIDER_CONFIG = {
docs: 'https://cloud.tencent.com/document/product/1772',
models: 'https://console.cloud.tencent.com/tione/v2/aimarket'
}
+ },
+ gpustack: {
+ api: {
+ url: ''
+ },
+ websites: {
+ official: 'https://gpustack.ai/',
+ docs: 'https://docs.gpustack.ai/latest/',
+ models: 'https://docs.gpustack.ai/latest/overview/#supported-models'
+ }
}
}
diff --git a/src/renderer/src/hooks/useGPUStack.ts b/src/renderer/src/hooks/useGPUStack.ts
new file mode 100644
index 00000000..a349a6ee
--- /dev/null
+++ b/src/renderer/src/hooks/useGPUStack.ts
@@ -0,0 +1,18 @@
+import store, { useAppSelector } from '@renderer/store'
+import { setGPUStackKeepAliveTime } from '@renderer/store/llm'
+import { useDispatch } from 'react-redux'
+
+export function useGPUStackSettings() {
+ const settings = useAppSelector((state) => state.llm.settings.gpustack)
+ const dispatch = useDispatch()
+
+ return { ...settings, setKeepAliveTime: (time: number) => dispatch(setGPUStackKeepAliveTime(time)) }
+}
+
+export function getGPUStackSettings() {
+ return store.getState().llm.settings.gpustack
+}
+
+export function getGPUStackKeepAliveTime() {
+ return store.getState().llm.settings.gpustack.keepAliveTime + 'm'
+}
diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json
index 7343f2d0..e98682ad 100644
--- a/src/renderer/src/i18n/locales/en-us.json
+++ b/src/renderer/src/i18n/locales/en-us.json
@@ -561,6 +561,12 @@
},
"title": "PlantUML Diagram"
},
+ "gpustack": {
+ "keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes.",
+ "keep_alive_time.placeholder": "Minutes",
+ "keep_alive_time.title": "Keep Alive Time",
+ "title": "GPUStack"
+ },
"prompts": {
"explanation": "Explain this concept to me",
"summarize": "Summarize this text",
@@ -608,7 +614,8 @@
"xirang": "State Cloud Xirang",
"yi": "Yi",
"zhinao": "360AI",
- "zhipu": "ZHIPU AI"
+ "zhipu": "ZHIPU AI",
+ "gpustack": "GPUStack"
},
"restore": {
"confirm": "Are you sure you want to restore data?",
diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json
index ed10399e..d83eac4d 100644
--- a/src/renderer/src/i18n/locales/ja-jp.json
+++ b/src/renderer/src/i18n/locales/ja-jp.json
@@ -561,6 +561,12 @@
},
"title": "PlantUML 図表"
},
+ "gpustack": {
+ "keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)",
+ "keep_alive_time.placeholder": "分",
+ "keep_alive_time.title": "保持時間",
+ "title": "GPUStack"
+ },
"prompts": {
"explanation": "この概念を説明してください",
"summarize": "このテキストを要約してください",
@@ -608,7 +614,8 @@
"xirang": "天翼クラウド 息壤",
"yi": "零一万物",
"zhinao": "360智脳",
- "zhipu": "智譜AI"
+ "zhipu": "智譜AI",
+ "gpustack": "GPUStack"
},
"restore": {
"confirm": "データを復元しますか?",
diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json
index 289e3672..cf297eb2 100644
--- a/src/renderer/src/i18n/locales/ru-ru.json
+++ b/src/renderer/src/i18n/locales/ru-ru.json
@@ -388,6 +388,12 @@
},
"title": "Диаграмма Mermaid"
},
+ "gpustack": {
+ "keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
+ "keep_alive_time.placeholder": "Минуты",
+ "keep_alive_time.title": "Время жизни модели",
+ "title": "GPUStack"
+ },
"message": {
"api.check.model.title": "Выберите модель для проверки",
"api.connection.failed": "Соединение не удалось",
@@ -608,7 +614,8 @@
"xirang": "State Cloud Xirang",
"yi": "Yi",
"zhinao": "360AI",
- "zhipu": "ZHIPU AI"
+ "zhipu": "ZHIPU AI",
+ "gpustack": "GPUStack"
},
"restore": {
"confirm": "Вы уверены, что хотите восстановить данные?",
diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json
index d7d9d5e9..fe9f5ffa 100644
--- a/src/renderer/src/i18n/locales/zh-cn.json
+++ b/src/renderer/src/i18n/locales/zh-cn.json
@@ -608,7 +608,8 @@
"xirang": "天翼云息壤",
"yi": "零一万物",
"zhinao": "360智脑",
- "zhipu": "智谱AI"
+ "zhipu": "智谱AI",
+ "gpustack": "GPUStack"
},
"restore": {
"confirm": "确定要恢复数据吗?",
@@ -624,6 +625,12 @@
},
"title": "数据恢复"
},
+ "gpustack": {
+ "keep_alive_time.description": "模型在内存中保持的时间(默认:5分钟)",
+ "keep_alive_time.placeholder": "分钟",
+ "keep_alive_time.title": "保持活跃时间",
+ "title": "GPUStack"
+ },
"settings": {
"about": "关于我们",
"about.checkingUpdate": "正在检查更新...",
diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json
index 5dea8ce3..02553ebf 100644
--- a/src/renderer/src/i18n/locales/zh-tw.json
+++ b/src/renderer/src/i18n/locales/zh-tw.json
@@ -608,7 +608,8 @@
"xirang": "天翼雲息壤",
"yi": "零一萬物",
"zhinao": "360 智腦",
- "zhipu": "智譜 AI"
+ "zhipu": "智譜 AI",
+ "gpustack": "GPUStack"
},
"restore": {
"confirm": "確定要復原資料嗎?",
@@ -624,6 +625,12 @@
},
"title": "資料復原"
},
+ "gpustack": {
+ "keep_alive_time.description": "模型在記憶體中保持的時間(預設為 5 分鐘)。",
+ "keep_alive_time.placeholder": "分鐘",
+ "keep_alive_time.title": "保持活躍時間",
+ "title": "GPUStack"
+ },
"settings": {
"about": "關於與回饋",
"about.checkingUpdate": "正在檢查更新...",
diff --git a/src/renderer/src/pages/settings/ProviderSettings/GPUStackSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/GPUStackSettings.tsx
new file mode 100644
index 00000000..5dc9ec74
--- /dev/null
+++ b/src/renderer/src/pages/settings/ProviderSettings/GPUStackSettings.tsx
@@ -0,0 +1,34 @@
+import { useGPUStackSettings } from '@renderer/hooks/useGPUStack'
+import { InputNumber } from 'antd'
+import { FC, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import styled from 'styled-components'
+
+import { SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '..'
+
+const GPUStackSettings: FC = () => {
+ const { keepAliveTime, setKeepAliveTime } = useGPUStackSettings()
+ const [keepAliveMinutes, setKeepAliveMinutes] = useState(keepAliveTime)
+ const { t } = useTranslation()
+
+ return (
+
+ {t('gpustack.keep_alive_time.title')}
+ setKeepAliveMinutes(Number(e))}
+ onBlur={() => setKeepAliveTime(keepAliveMinutes)}
+ suffix={t('gpustack.keep_alive_time.placeholder')}
+ step={5}
+ />
+
+ {t('gpustack.keep_alive_time.description')}
+
+
+ )
+}
+
+const Container = styled.div``
+
+export default GPUStackSettings
diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx
index 6ddd8caf..f61c6721 100644
--- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx
+++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx
@@ -28,6 +28,7 @@ import {
SettingTitle
} from '..'
import ApiCheckPopup from './ApiCheckPopup'
+import GPUStackSettings from './GPUStackSettings'
import GraphRAGSettings from './GraphRAGSettings'
import HealthCheckPopup from './HealthCheckPopup'
import LMStudioSettings from './LMStudioSettings'
@@ -345,6 +346,7 @@ const ProviderSetting: FC = ({ provider: _provider }) => {
)}
{provider.id === 'ollama' && }
{provider.id === 'lmstudio' && }
+ {provider.id === 'gpustack' && }
{provider.id === 'graphrag-kylin-mountain' && provider.models.length > 0 && (
)}
diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts
index 2357ab2b..da059823 100644
--- a/src/renderer/src/store/index.ts
+++ b/src/renderer/src/store/index.ts
@@ -36,7 +36,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
- version: 78,
+ version: 79,
blacklist: ['runtime', 'messages'],
migrate
},
diff --git a/src/renderer/src/store/llm.ts b/src/renderer/src/store/llm.ts
index fd7c932f..cdc32582 100644
--- a/src/renderer/src/store/llm.ts
+++ b/src/renderer/src/store/llm.ts
@@ -11,6 +11,9 @@ type LlmSettings = {
lmstudio: {
keepAliveTime: number
}
+ gpustack: {
+ keepAliveTime: number
+ }
}
export interface LlmState {
@@ -426,6 +429,16 @@ const initialState: LlmState = {
models: SYSTEM_MODELS['tencent-cloud-ti'],
isSystem: true,
enabled: false
+ },
+ {
+ id: 'gpustack',
+ name: 'GPUStack',
+ type: 'openai',
+ apiKey: '',
+ apiHost: '',
+ models: SYSTEM_MODELS.gpustack,
+ isSystem: true,
+ enabled: false
}
],
settings: {
@@ -434,6 +447,9 @@ const initialState: LlmState = {
},
lmstudio: {
keepAliveTime: 0
+ },
+ gpustack: {
+ keepAliveTime: 0
}
}
}
@@ -462,6 +478,9 @@ const getIntegratedInitialState = () => {
},
lmstudio: {
keepAliveTime: 3600
+ },
+ gpustack: {
+ keepAliveTime: 3600
}
}
} as LlmState
@@ -534,6 +553,9 @@ const settingsSlice = createSlice({
setLMStudioKeepAliveTime: (state, action: PayloadAction) => {
state.settings.lmstudio.keepAliveTime = action.payload
},
+ setGPUStackKeepAliveTime: (state, action: PayloadAction) => {
+ state.settings.gpustack.keepAliveTime = action.payload
+ },
updateModel: (
state,
action: PayloadAction<{
@@ -564,6 +586,7 @@ export const {
setTranslateModel,
setOllamaKeepAliveTime,
setLMStudioKeepAliveTime,
+ setGPUStackKeepAliveTime,
updateModel
} = settingsSlice.actions
diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts
index a4621ec4..575c5c55 100644
--- a/src/renderer/src/store/migrate.ts
+++ b/src/renderer/src/store/migrate.ts
@@ -1243,6 +1243,19 @@ const migrateConfig = {
state.llm.providers = moveProvider(state.llm.providers, 'infini', 10)
removeMiniAppIconsFromState(state)
return state
+ },
+ '79': (state: RootState) => {
+ state.llm.providers.push({
+ id: 'gpustack',
+ name: 'GPUStack',
+ type: 'openai',
+ apiKey: '',
+ apiHost: '',
+ models: SYSTEM_MODELS.gpustack,
+ isSystem: true,
+ enabled: false
+ })
+ return state
}
}