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 @@ + + + Combined Shape + + + + + + + + + + + \ 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 } }