From fd4334f33133ef8df3128cb51e462eeb7c137b48 Mon Sep 17 00:00:00 2001 From: hehua2008 Date: Fri, 14 Feb 2025 10:49:57 +0800 Subject: [PATCH] feat: Add LM Studio support (#1572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hehua2008 Co-authored-by: 亢奋猫 --- README.md | 2 +- docs/README.ja.md | 2 +- docs/README.zh.md | 2 +- .../src/assets/images/providers/lmstudio.png | Bin 0 -> 13508 bytes src/renderer/src/config/models.ts | 1 + src/renderer/src/config/providers.ts | 13 +++++++ src/renderer/src/hooks/useLMStudio.ts | 18 +++++++++ src/renderer/src/i18n/locales/en-us.json | 7 ++++ src/renderer/src/i18n/locales/ja-jp.json | 7 ++++ src/renderer/src/i18n/locales/ru-ru.json | 7 ++++ src/renderer/src/i18n/locales/zh-cn.json | 7 ++++ src/renderer/src/i18n/locales/zh-tw.json | 7 ++++ .../ProviderSettings/LMStudioSettings.tsx | 34 +++++++++++++++++ .../ProviderSettings/ProviderSetting.tsx | 2 + src/renderer/src/providers/BaseProvider.ts | 3 +- src/renderer/src/services/ApiService.ts | 4 +- src/renderer/src/store/llm.ts | 35 +++++++++++++++++- src/renderer/src/store/migrate.ts | 15 ++++++++ yarn.lock | 3 ++ 19 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 src/renderer/src/assets/images/providers/lmstudio.png create mode 100644 src/renderer/src/hooks/useLMStudio.ts create mode 100644 src/renderer/src/pages/settings/ProviderSettings/LMStudioSettings.tsx diff --git a/README.md b/README.md index 1ade0b9a..c055797b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Cherry Studio is a desktop client that supports for multiple LLM providers, avai - ☁️ Major LLM Cloud Services: OpenAI, Gemini, Anthropic, and more - 🔗 AI Web Service Integration: Claude, Peplexity, Poe, and others - - 💻 Local Model Support with Ollama + - 💻 Local Model Support with Ollama, LM Studio 2. **AI Assistants & Conversations**: diff --git a/docs/README.ja.md b/docs/README.ja.md index e38bdbff..9ba1c69f 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -31,7 +31,7 @@ Cherry Studioは、複数のLLMプロバイダーをサポートするデスク - ☁️ 主要な LLM クラウドサービス対応:OpenAI、Gemini、Anthropic など - 🔗 AI Web サービス統合:Claude、Peplexity、Poe など - - 💻 Ollama によるローカルモデル実行対応 + - 💻 Ollama、LM Studio によるローカルモデル実行対応 2. **AI アシスタントと対話**: diff --git a/docs/README.zh.md b/docs/README.zh.md index 40da0a77..730b1e93 100644 --- a/docs/README.zh.md +++ b/docs/README.zh.md @@ -31,7 +31,7 @@ Cherry Studio 是一款支持多个大语言模型(LLM)服务商的桌面客 - ☁️ 支持主流 LLM 云服务:OpenAI、Gemini、Anthropic、硅基流动等 - 🔗 集成流行 AI Web 服务:Claude、Peplexity、Poe、腾讯元宝、知乎直答等 - - 💻 支持 Ollama 本地模型部署 + - 💻 支持 Ollama、LM Studio 本地模型部署 2. **智能助手与对话**: diff --git a/src/renderer/src/assets/images/providers/lmstudio.png b/src/renderer/src/assets/images/providers/lmstudio.png new file mode 100644 index 0000000000000000000000000000000000000000..6f5fc077f78f2ae6c26e78b24bf420220b484a60 GIT binary patch literal 13508 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 literal 0 HcmV?d00001 diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index 3bc3870c..fdebee5b 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -323,6 +323,7 @@ export const SYSTEM_MODELS: Record = { } ], ollama: [], + lmstudio: [], silicon: [ { id: 'deepseek-ai/DeepSeek-R1', diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 9fa59ae7..28392e52 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -24,6 +24,7 @@ import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.png import NvidiaProviderLogo from '@renderer/assets/images/providers/nvidia.png' import OcoolAiProviderLogo from '@renderer/assets/images/providers/ocoolai.png' import OllamaProviderLogo from '@renderer/assets/images/providers/ollama.png' +import LMStudioProviderLogo from '@renderer/assets/images/providers/lmstudio.png' import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png' import OpenRouterProviderLogo from '@renderer/assets/images/providers/openrouter.png' import PerplexityProviderLogo from '@renderer/assets/images/providers/perplexity.png' @@ -52,6 +53,8 @@ export function getProviderLogo(providerId: string) { return ZhipuProviderLogo case 'ollama': return OllamaProviderLogo + case 'lmstudio': + return LMStudioProviderLogo case 'moonshot': return MoonshotProviderLogo case 'openrouter': @@ -373,6 +376,16 @@ export const PROVIDER_CONFIG = { models: 'https://ollama.com/library' } }, + lmstudio: { + api: { + url: 'http://localhost:1234' + }, + websites: { + official: 'https://lmstudio.ai/', + docs: 'https://lmstudio.ai/docs', + models: 'https://lmstudio.ai/models' + } + }, anthropic: { api: { url: 'https://api.anthropic.com/' diff --git a/src/renderer/src/hooks/useLMStudio.ts b/src/renderer/src/hooks/useLMStudio.ts new file mode 100644 index 00000000..687ed4ed --- /dev/null +++ b/src/renderer/src/hooks/useLMStudio.ts @@ -0,0 +1,18 @@ +import store, { useAppSelector } from '@renderer/store' +import { setLMStudioKeepAliveTime } from '@renderer/store/llm' +import { useDispatch } from 'react-redux' + +export function useLMStudioSettings() { + const settings = useAppSelector((state) => state.llm.settings.lmstudio) + const dispatch = useDispatch() + + return { ...settings, setKeepAliveTime: (time: number) => dispatch(setLMStudioKeepAliveTime(time)) } +} + +export function getLMStudioSettings() { + return store.getState().llm.settings.lmstudio +} + +export function getLMStudioKeepAliveTime() { + return store.getState().llm.settings.lmstudio.keepAliveTime + 'm' +} diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 24b723c6..30d3a06a 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -439,6 +439,12 @@ "keep_alive_time.title": "Keep Alive Time", "title": "Ollama" }, + "lmstudio": { + "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": "LM Studio" + }, "paintings": { "button.delete.image": "Delete Image", "button.delete.image.confirm": "Are you sure you want to delete this image?", @@ -493,6 +499,7 @@ "nvidia": "Nvidia", "ocoolai": "ocoolAI", "ollama": "Ollama", + "lmstudio": "LM Studio", "openai": "OpenAI", "openrouter": "OpenRouter", "ppio": "PPIO", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 50f4bf89..243cbe77 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -439,6 +439,12 @@ "keep_alive_time.title": "保持時間", "title": "Ollama" }, + "lmstudio": { + "keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)", + "keep_alive_time.placeholder": "分", + "keep_alive_time.title": "保持時間", + "title": "LM Studio" + }, "paintings": { "button.delete.image": "画像を削除", "button.delete.image.confirm": "この画像を削除してもよろしいですか?", @@ -493,6 +499,7 @@ "nvidia": "NVIDIA", "ocoolai": "ocoolAI", "ollama": "Ollama", + "lmstudio": "LM Studio", "openai": "OpenAI", "openrouter": "OpenRouter", "qwenlm": "QwenLM", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 110aee9c..f83a3599 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -439,6 +439,12 @@ "keep_alive_time.title": "Время жизни модели", "title": "Ollama" }, + "lmstudio": { + "keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.", + "keep_alive_time.placeholder": "Минуты", + "keep_alive_time.title": "Время жизни модели", + "title": "LM Studio" + }, "paintings": { "button.delete.image": "Удалить изображение", "button.delete.image.confirm": "Вы уверены, что хотите удалить это изображение?", @@ -493,6 +499,7 @@ "nvidia": "Nvidia", "ocoolai": "ocoolAI", "ollama": "Ollama", + "lmstudio": "LM Studio", "openai": "OpenAI", "openrouter": "OpenRouter", "qwenlm": "QwenLM", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 1e844b96..e5f7b564 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -439,6 +439,12 @@ "keep_alive_time.title": "保持活跃时间", "title": "Ollama" }, + "lmstudio": { + "keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5分钟)", + "keep_alive_time.placeholder": "分钟", + "keep_alive_time.title": "保持活跃时间", + "title": "LM Studio" + }, "paintings": { "button.delete.image": "删除图片", "button.delete.image.confirm": "确定要删除此图片吗?", @@ -493,6 +499,7 @@ "nvidia": "英伟达", "ocoolai": "ocoolAI", "ollama": "Ollama", + "lmstudio": "LM Studio", "openai": "OpenAI", "openrouter": "OpenRouter", "ppio": "PPIO 派欧云", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index bacf5fcc..5577bd0f 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -439,6 +439,12 @@ "keep_alive_time.title": "保持活躍時間", "title": "Ollama" }, + "lmstudio": { + "keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。", + "keep_alive_time.placeholder": "分鐘", + "keep_alive_time.title": "保持活躍時間", + "title": "LM Studio" + }, "paintings": { "infini": "無問芯穹", "perplexity": "Perplexity", @@ -493,6 +499,7 @@ "nvidia": "輝達", "ocoolai": "ocoolAI", "ollama": "Ollama", + "lmstudio": "LM Studio", "openai": "OpenAI", "openrouter": "OpenRouter", "ppio": "PPIO 派歐雲", diff --git a/src/renderer/src/pages/settings/ProviderSettings/LMStudioSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/LMStudioSettings.tsx new file mode 100644 index 00000000..9b7adf2b --- /dev/null +++ b/src/renderer/src/pages/settings/ProviderSettings/LMStudioSettings.tsx @@ -0,0 +1,34 @@ +import { useLMStudioSettings } from '@renderer/hooks/useLMStudio' +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 LMStudioSettings: FC = () => { + const { keepAliveTime, setKeepAliveTime } = useLMStudioSettings() + const [keepAliveMinutes, setKeepAliveMinutes] = useState(keepAliveTime) + const { t } = useTranslation() + + return ( + + {t('lmstudio.keep_alive_time.title')} + setKeepAliveMinutes(Number(e))} + onBlur={() => setKeepAliveTime(keepAliveMinutes)} + suffix={t('lmstudio.keep_alive_time.placeholder')} + step={5} + /> + + {t('lmstudio.keep_alive_time.description')} + + + ) +} + +const Container = styled.div`` + +export default LMStudioSettings diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 1ca7a2d9..af03369b 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -43,6 +43,7 @@ import ApiCheckPopup from './ApiCheckPopup' import EditModelsPopup from './EditModelsPopup' import GraphRAGSettings from './GraphRAGSettings' import OllamSettings from './OllamaSettings' +import LMStudioSettings from './LMStudioSettings' import SelectProviderModelPopup from './SelectProviderModelPopup' interface Props { @@ -319,6 +320,7 @@ const ProviderSetting: FC = ({ provider: _provider }) => { )} {provider.id === 'ollama' && } + {provider.id === 'lmstudio' && } {provider.id === 'graphrag-kylin-mountain' && provider.models.length > 0 && ( )} diff --git a/src/renderer/src/providers/BaseProvider.ts b/src/renderer/src/providers/BaseProvider.ts index e359e376..54128898 100644 --- a/src/renderer/src/providers/BaseProvider.ts +++ b/src/renderer/src/providers/BaseProvider.ts @@ -1,5 +1,6 @@ import { REFERENCE_PROMPT } from '@renderer/config/prompts' import { getOllamaKeepAliveTime } from '@renderer/hooks/useOllama' +import { getLMStudioKeepAliveTime } from '@renderer/hooks/useLMStudio' import { getKnowledgeReferences } from '@renderer/services/KnowledgeService' import store from '@renderer/store' import { Assistant, GenerateImageParams, Message, Model, Provider, Suggestion } from '@renderer/types' @@ -63,7 +64,7 @@ export default abstract class BaseProvider { } public get keepAliveTime() { - return this.provider.id === 'ollama' ? getOllamaKeepAliveTime() : undefined + return this.provider.id === 'ollama' ? getOllamaKeepAliveTime() : this.provider.id === 'lmstudio' ? getLMStudioKeepAliveTime() : undefined } public async fakeCompletions({ onChunk }: CompletionsParams) { diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index d4378923..7700d540 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -214,7 +214,7 @@ export async function checkApi(provider: Provider, model: Model) { const key = 'api-check' const style = { marginTop: '3vh' } - if (provider.id !== 'ollama') { + if (provider.id !== 'ollama' && provider.id !== 'lmstudio') { if (!provider.apiKey) { window.message.error({ content: i18n.t('message.error.enter.api.key'), key, style }) return { @@ -252,7 +252,7 @@ export async function checkApi(provider: Provider, model: Model) { function hasApiKey(provider: Provider) { if (!provider) return false - if (provider.id === 'ollama') return true + if (provider.id === 'ollama' || provider.id === 'lmstudio') return true return !isEmpty(provider.apiKey) } diff --git a/src/renderer/src/store/llm.ts b/src/renderer/src/store/llm.ts index cec928cc..0fa68195 100644 --- a/src/renderer/src/store/llm.ts +++ b/src/renderer/src/store/llm.ts @@ -8,6 +8,9 @@ type LlmSettings = { ollama: { keepAliveTime: number } + lmstudio: { + keepAliveTime: number + } } export interface LlmState { @@ -83,6 +86,16 @@ const initialState: LlmState = { isSystem: true, enabled: false }, + { + id: 'lmstudio', + name: 'LM Studio', + type: 'openai', + apiKey: '', + apiHost: 'http://localhost:1234', + models: SYSTEM_MODELS.lmstudio, + isSystem: true, + enabled: false + }, { id: 'anthropic', name: 'Anthropic', @@ -378,6 +391,9 @@ const initialState: LlmState = { settings: { ollama: { keepAliveTime: 0 + }, + lmstudio: { + keepAliveTime: 0 } } } @@ -398,11 +414,24 @@ const getIntegratedInitialState = () => { models: [model], isSystem: true, enabled: true + }, + { + id: 'lmstudio', + name: 'LM Studio', + type: 'openai', + apiKey: '', + apiHost: 'http://localhost:1234', + models: [model], + isSystem: true, + enabled: true } ], settings: { ollama: { keepAliveTime: 3600 + }, + lmstudio: { + keepAliveTime: 3600 } } } as LlmState @@ -457,6 +486,9 @@ const settingsSlice = createSlice({ }, setOllamaKeepAliveTime: (state, action: PayloadAction) => { state.settings.ollama.keepAliveTime = action.payload + }, + setLMStudioKeepAliveTime: (state, action: PayloadAction) => { + state.settings.lmstudio.keepAliveTime = action.payload } } }) @@ -471,7 +503,8 @@ export const { setDefaultModel, setTopicNamingModel, setTranslateModel, - setOllamaKeepAliveTime + setOllamaKeepAliveTime, + setLMStudioKeepAliveTime } = settingsSlice.actions export default settingsSlice.reducer diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index e7e06555..f43bf3a6 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -970,6 +970,16 @@ const migrateConfig = { } state.llm.providers.push( + { + id: 'lmstudio', + name: 'LM Studio', + type: 'openai', + apiKey: '', + apiHost: 'http://localhost:1234', + models: SYSTEM_MODELS.lmstudio, + isSystem: true, + enabled: false + }, { id: 'perplexity', name: 'Perplexity', @@ -1001,6 +1011,11 @@ const migrateConfig = { enabled: false } ) + + state.llm.settings.lmstudio = { + keepAliveTime: 5 + } + return state } } diff --git a/yarn.lock b/yarn.lock index 21f845a7..36bc3c83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8330,6 +8330,7 @@ __metadata: "@langchain/google-vertexai": "*" "@langchain/google-vertexai-web": "*" "@langchain/groq": "*" + "@langchain/lmstudio": "*" "@langchain/mistralai": "*" "@langchain/ollama": "*" axios: "*" @@ -8356,6 +8357,8 @@ __metadata: optional: true "@langchain/groq": optional: true + "@langchain/lmstudio": + optional: true "@langchain/mistralai": optional: true "@langchain/ollama":