Skip to content

Commit a2b8007

Browse files
authored
Merge pull request #149 from pmbstyle/codex/add-deepseek-provider
Add DeepSeek provider
2 parents 00ed8b2 + 11e576a commit a2b8007

21 files changed

Lines changed: 557 additions & 15 deletions

.env-example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#OpenAI API Key
22
VITE_OPENAI_API_KEY = ''
33

4+
#DeepSeek API Key
5+
VITE_DEEPSEEK_API_KEY = ''
6+
47
#Groq whisper API Key
58
VITE_GROQ_API_KEY = ''
69

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ With the local STT model, you can set a **wake-up word** (like "Hey, Siri").
8484

8585
Fully customizable settings interface:
8686

87-
* LLM provider selection between OpenAI, OpenRouter, Z.ai(coding plan), Minimax(token plan), Ollama, LM Studio
87+
* LLM provider selection between OpenAI, OpenRouter, DeepSeek, Z.ai(coding plan), Minimax(token plan), Ollama, LM Studio
8888
* Cloud or local TTS, STT, Embeddings
8989
* Model choice & parameters (temperature, top\_p, history, etc)
9090
* Prompt and summarization tuning
@@ -131,7 +131,7 @@ Follow the [Setup Instructions](https://github.com/pmbstyle/Alice/blob/main/docs
131131
* **Frontend:** [Vue.js](https://vuejs.org/), [TailwindCSS](https://tailwindcss.com/)
132132
* **Desktop Shell:** [Electron](https://www.electronjs.org/)
133133
* **State Management:** [Pinia](https://pinia.vuejs.org/)
134-
* **AI APIs:** [OpenAI](https://platform.openai.com/), [OpenRouter](https://openrouter.ai/), [Groq](https://console.groq.com/)
134+
* **AI APIs:** [OpenAI](https://platform.openai.com/), [OpenRouter](https://openrouter.ai/), [DeepSeek](https://platform.deepseek.com/), [Groq](https://console.groq.com/)
135135
* **Backend:** [Go](https://go.dev/)
136136
* **Vector search engine**: [hnswlib-node](https://github.com/nmslib/hnswlib)
137137
* **Local storage**: [better-sqlite3](https://github.com/WiseLibs/better-sqlite3)

docs/setupInstructions.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Run in your terminal `xattr -cr "/Applications/Alice AI App.app"`
1414

1515
# AI Provider Setup
1616

17-
Alice supports OpenAI, OpenRouter and local LLM inference.
17+
Alice supports OpenAI, OpenRouter, DeepSeek, MiniMax, Z.ai, and local LLM inference.
1818

1919
## OpenAI (Default)
2020

@@ -31,6 +31,14 @@ Alice supports OpenAI, OpenRouter and local LLM inference.
3131
- Models automatically include web search capabilities
3232
- No image generation support (use OpenAI provider for image-gen)
3333

34+
## DeepSeek (Alternative)
35+
36+
- Go to [DeepSeek API Platform](https://platform.deepseek.com/api_keys) to get your DeepSeek API key
37+
- Select "AI Provider" in Core Settings as DeepSeek
38+
- Use the default base URL `https://api.deepseek.com`
39+
- DeepSeek powers chat inference only; cloud TTS/STT/embeddings still require OpenAI or local voice and memory mode
40+
- No image generation support (use OpenAI provider for image-gen)
41+
3442
## Local Ollama / LM studio
3543

3644
- Run Ollama or LM studio

electron/main/settingsManager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface AppSettings {
1313
VITE_OPENROUTER_API_KEY?: string
1414
VITE_ZAI_API_KEY?: string
1515
VITE_MINIMAX_API_KEY?: string
16+
VITE_DEEPSEEK_API_KEY?: string
1617
VITE_GROQ_API_KEY?: string
1718
sttProvider?: 'openai' | 'groq' | 'transformers'
1819
aiProvider?:
@@ -22,6 +23,7 @@ export interface AppSettings {
2223
| 'lm-studio'
2324
| 'zai'
2425
| 'minimax'
26+
| 'deepseek'
2527

2628
// Transformers STT settings
2729
transformersModel?: string
@@ -35,6 +37,7 @@ export interface AppSettings {
3537
lmStudioBaseUrl?: string
3638
zaiBaseUrl?: string
3739
minimaxBaseUrl?: string
40+
deepseekBaseUrl?: string
3841

3942
assistantModel?: string
4043
assistantSystemPrompt?: string

src/components/settings/AssistantSettingsTab.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@
6868
(currentSettings.aiProvider === 'minimax' &&
6969
currentSettings.VITE_MINIMAX_API_KEY &&
7070
currentSettings.minimaxBaseUrl) ||
71+
(currentSettings.aiProvider === 'deepseek' &&
72+
currentSettings.VITE_DEEPSEEK_API_KEY &&
73+
currentSettings.deepseekBaseUrl) ||
7174
(currentSettings.aiProvider === 'ollama' &&
7275
currentSettings.ollamaBaseUrl) ||
7376
(currentSettings.aiProvider === 'lm-studio' &&
@@ -306,6 +309,9 @@
306309
(currentSettings.aiProvider === 'minimax' &&
307310
currentSettings.VITE_MINIMAX_API_KEY &&
308311
currentSettings.minimaxBaseUrl) ||
312+
(currentSettings.aiProvider === 'deepseek' &&
313+
currentSettings.VITE_DEEPSEEK_API_KEY &&
314+
currentSettings.deepseekBaseUrl) ||
309315
(currentSettings.aiProvider === 'ollama' &&
310316
currentSettings.ollamaBaseUrl) ||
311317
(currentSettings.aiProvider === 'lm-studio' &&
@@ -519,6 +525,7 @@ const getProviderDisplayName = (provider: string): string => {
519525
'lm-studio': 'LM Studio',
520526
zai: 'Z.ai',
521527
minimax: 'MiniMax',
528+
deepseek: 'DeepSeek',
522529
}
523530
return providerNames[provider] || provider
524531
}

src/components/settings/CoreSettingsTab.vue

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<option value="openrouter">OpenRouter</option>
2222
<option value="zai">Z.ai (Coding Plan)</option>
2323
<option value="minimax">MiniMax</option>
24+
<option value="deepseek">DeepSeek</option>
2425
<option value="ollama">Ollama (Local)</option>
2526
<option value="lm-studio">LM Studio (Local)</option>
2627
</select>
@@ -197,6 +198,37 @@
197198
OpenAI-compatible endpoint for MiniMax token/coding plans.
198199
</p>
199200
</div>
201+
<div v-if="currentSettings.aiProvider === 'deepseek'">
202+
<label for="deepseek-key" class="block mb-1 text-sm"
203+
>DeepSeek API Key *</label
204+
>
205+
<input
206+
id="deepseek-key"
207+
type="password"
208+
v-model="currentSettings.VITE_DEEPSEEK_API_KEY"
209+
class="input focus:outline-none w-full"
210+
autocomplete="new-password"
211+
placeholder="sk-..."
212+
/>
213+
<p class="text-xs text-gray-400 mt-1">
214+
Required for DeepSeek chat models.
215+
</p>
216+
</div>
217+
<div v-if="currentSettings.aiProvider === 'deepseek'">
218+
<label for="deepseek-url" class="block mb-1 text-sm"
219+
>DeepSeek Base URL *</label
220+
>
221+
<input
222+
id="deepseek-url"
223+
type="text"
224+
v-model="currentSettings.deepseekBaseUrl"
225+
class="input focus:outline-none w-full"
226+
placeholder="https://api.deepseek.com"
227+
/>
228+
<p class="text-xs text-gray-400 mt-1">
229+
OpenAI-compatible endpoint for DeepSeek chat completions.
230+
</p>
231+
</div>
200232
<div v-if="currentSettings.aiProvider === 'lm-studio'">
201233
<label for="lmstudio-url" class="block mb-1 text-sm"
202234
>LM Studio Base URL *</label

src/components/wizard/OnboardingWizard.vue

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
@test-openrouter="testOpenRouterKey"
2525
@test-zai="testZAIKey"
2626
@test-minimax="testMiniMaxKey"
27+
@test-deepseek="testDeepSeekKey"
2728
@test-ollama="testOllamaConnection"
2829
@test-lmstudio="testLMStudioConnection"
2930
@reset-tests="resetTestResults"
@@ -66,11 +67,13 @@ import VoiceModelsStep from './steps/VoiceModelsStep.vue'
6667
import FinalSetupStep from './steps/FinalSetupStep.vue'
6768
import OpenAI from 'openai'
6869
import {
70+
DEEPSEEK_OPENAI_BASE_URL,
6971
MINIMAX_OPENAI_BASE_URL,
7072
PROVIDER_CONFIGS,
7173
ZAI_CODING_BASE_URL,
7274
type AIProviderKey,
7375
} from '../../services/llmProviders/providerCatalog'
76+
import { listDeepSeekModelsForConfig } from '../../services/llmProviders/deepseek'
7477
import { listMiniMaxModelsForConfig } from '../../services/llmProviders/minimax'
7578
import { listOpenAIModelsForConfig } from '../../services/llmProviders/openai'
7679
import { listOpenRouterModelsForConfig } from '../../services/llmProviders/openrouter'
@@ -111,6 +114,7 @@ const formData = reactive({
111114
VITE_OPENROUTER_API_KEY: '',
112115
VITE_ZAI_API_KEY: '',
113116
VITE_MINIMAX_API_KEY: '',
117+
VITE_DEEPSEEK_API_KEY: '',
114118
aiProvider: 'openai' as AIProviderKey,
115119
assistantModel: openaiDefaults.assistantModel as string,
116120
summarizationModel: openaiDefaults.summarizationModel as string,
@@ -123,6 +127,7 @@ const formData = reactive({
123127
lmStudioBaseUrl: 'http://localhost:1234',
124128
zaiBaseUrl: ZAI_CODING_BASE_URL,
125129
minimaxBaseUrl: MINIMAX_OPENAI_BASE_URL,
130+
deepseekBaseUrl: DEEPSEEK_OPENAI_BASE_URL,
126131
useLocalModels: false,
127132
availableModels: [] as string[],
128133
localSttLanguage: 'auto',
@@ -133,6 +138,7 @@ const isTesting = reactive({
133138
openrouter: false,
134139
zai: false,
135140
minimax: false,
141+
deepseek: false,
136142
ollama: false,
137143
lmStudio: false,
138144
})
@@ -142,6 +148,7 @@ const testResult = reactive({
142148
openrouter: { success: false, error: '' },
143149
zai: { success: false, error: '' },
144150
minimax: { success: false, error: '' },
151+
deepseek: { success: false, error: '' },
145152
ollama: { success: false, error: '' },
146153
lmStudio: { success: false, error: '' },
147154
})
@@ -173,7 +180,8 @@ const canContinue = computed(() => {
173180
formData.aiProvider === 'lm-studio' ||
174181
formData.aiProvider === 'openrouter' ||
175182
formData.aiProvider === 'zai' ||
176-
formData.aiProvider === 'minimax') &&
183+
formData.aiProvider === 'minimax' ||
184+
formData.aiProvider === 'deepseek') &&
177185
!formData.VITE_OPENAI_API_KEY.trim()
178186
) {
179187
return false
@@ -245,6 +253,8 @@ const fetchAvailableModels = async () => {
245253
baseURL = formData.zaiBaseUrl
246254
} else if (formData.aiProvider === 'minimax') {
247255
baseURL = formData.minimaxBaseUrl
256+
} else if (formData.aiProvider === 'deepseek') {
257+
baseURL = formData.deepseekBaseUrl
248258
} else {
249259
return
250260
}
@@ -263,6 +273,20 @@ const fetchAvailableModels = async () => {
263273
return
264274
}
265275
276+
if (formData.aiProvider === 'deepseek') {
277+
const models = await listDeepSeekModelsForConfig(
278+
formData.VITE_DEEPSEEK_API_KEY,
279+
baseURL
280+
)
281+
formData.availableModels = models.map(model => model.id)
282+
283+
if (formData.availableModels.length > 0) {
284+
formData.assistantModel = formData.availableModels[0]
285+
formData.summarizationModel = formData.availableModels[0]
286+
}
287+
return
288+
}
289+
266290
if (formData.aiProvider === 'zai') {
267291
const models = await listZAIModelsForConfig(
268292
formData.VITE_ZAI_API_KEY,
@@ -414,6 +438,39 @@ const testMiniMaxKey = async () => {
414438
}
415439
}
416440
441+
const testDeepSeekKey = async () => {
442+
if (!formData.VITE_DEEPSEEK_API_KEY.trim()) {
443+
testResult.deepseek.error = 'API Key cannot be empty.'
444+
testResult.deepseek.success = false
445+
return
446+
}
447+
if (!formData.deepseekBaseUrl.trim()) {
448+
testResult.deepseek.error = 'Base URL cannot be empty.'
449+
testResult.deepseek.success = false
450+
return
451+
}
452+
453+
isTesting.deepseek = true
454+
testResult.deepseek.error = ''
455+
testResult.deepseek.success = false
456+
457+
try {
458+
await fetchAvailableModels()
459+
testResult.deepseek.success = true
460+
} catch (e: any) {
461+
testResult.deepseek.error =
462+
'API key or OpenAI-compatible endpoint is invalid or has no permissions.'
463+
if (e.message?.includes('401')) {
464+
testResult.deepseek.error = 'Invalid API key - please check your key.'
465+
} else if (e.message?.includes('429')) {
466+
testResult.deepseek.error =
467+
'Rate limit exceeded - please try again later.'
468+
}
469+
} finally {
470+
isTesting.deepseek = false
471+
}
472+
}
473+
417474
const testOllamaConnection = async () => {
418475
if (!formData.ollamaBaseUrl.trim()) {
419476
testResult.ollama.error = 'Ollama Base URL cannot be empty.'
@@ -499,6 +556,8 @@ const resetTestResults = () => {
499556
testResult.zai.error = ''
500557
testResult.minimax.success = false
501558
testResult.minimax.error = ''
559+
testResult.deepseek.success = false
560+
testResult.deepseek.error = ''
502561
testResult.ollama.success = false
503562
testResult.ollama.error = ''
504563
testResult.lmStudio.success = false
@@ -522,6 +581,12 @@ const isCurrentProviderTested = () => {
522581
Boolean(formData.assistantModel) &&
523582
Boolean(formData.summarizationModel)
524583
)
584+
} else if (formData.aiProvider === 'deepseek') {
585+
return (
586+
testResult.deepseek.success &&
587+
Boolean(formData.assistantModel) &&
588+
Boolean(formData.summarizationModel)
589+
)
525590
} else if (formData.aiProvider === 'ollama') {
526591
return (
527592
testResult.ollama.success &&

0 commit comments

Comments
 (0)