diff --git a/README.md b/README.md
index ff81bd047..4e7039280 100644
--- a/README.md
+++ b/README.md
@@ -143,7 +143,7 @@ You can easily configure 100s of AI models in big-AGI:
| **AI models** | _supported vendors_ |
|:--------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| Opensource Servers | [LocalAI](https://localai.com) (multimodal) · [Ollama](https://ollama.com/) · [Oobabooga](https://github.com/oobabooga/text-generation-webui) |
+| Opensource Servers | [LocalAI](https://localai.io/) (multimodal) · [Ollama](https://ollama.com/) · [Oobabooga](https://github.com/oobabooga/text-generation-webui) |
| Local Servers | [LM Studio](https://lmstudio.ai/) |
| Multimodal services | [Azure](https://azure.microsoft.com/en-us/products/ai-services/openai-service) · [Google Gemini](https://ai.google.dev/) · [OpenAI](https://platform.openai.com/docs/overview) |
| Language services | [Anthropic](https://anthropic.com) · [Groq](https://wow.groq.com/) · [Mistral](https://mistral.ai/) · [OpenRouter](https://openrouter.ai/) · [Perplexity](https://www.perplexity.ai/) · [Together AI](https://www.together.ai/) |
diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx
index adca71759..ad58caa14 100644
--- a/src/apps/chat/AppChat.tsx
+++ b/src/apps/chat/AppChat.tsx
@@ -19,7 +19,7 @@ import { ConfirmationModal } from '~/common/components/ConfirmationModal';
import { ConversationsManager } from '~/common/chats/ConversationsManager';
import { DConversation, DConversationId } from '~/common/stores/chat/chat.conversation';
import { DMessageAttachmentFragment, DMessageContentFragment, duplicateDMessageFragments } from '~/common/stores/chat/chat.fragments';
-import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
+import { GlobalShortcutDefinition, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
import { PanelResizeInset } from '~/common/components/panes/GoodPanelResizeHandler';
import { PreferencesTab, useOptimaLayout, usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
import { ScrollToBottom } from '~/common/scroll-to-bottom/ScrollToBottom';
@@ -394,7 +394,7 @@ export function AppChat() {
openLlmOptions(chatLLMId);
}, [openLlmOptions]);
- const shortcuts = React.useMemo((): GlobalShortcutItem[] => [
+ const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [
// focused conversation
['b', true, true, false, handleMessageBeamLastInFocusedPane],
['r', true, true, false, handleMessageRegenerateLastInFocusedPane],
diff --git a/src/apps/chat/components/ChatMessageList.tsx b/src/apps/chat/components/ChatMessageList.tsx
index 1583f5453..74a9386ad 100644
--- a/src/apps/chat/components/ChatMessageList.tsx
+++ b/src/apps/chat/components/ChatMessageList.tsx
@@ -11,7 +11,7 @@ import type { DConversationId } from '~/common/stores/chat/chat.conversation';
import type { DMessageFragment, DMessageFragmentId } from '~/common/stores/chat/chat.fragments';
import { InlineError } from '~/common/components/InlineError';
import { PreferencesTab, useOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
-import { ShortcutKeyName, useGlobalShortcut } from '~/common/components/useGlobalShortcut';
+import { ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
import { createDMessageTextContent, DMessageId, DMessageUserFlag, messageToggleUserFlag } from '~/common/stores/chat/chat.message';
import { getConversation, useChatStore } from '~/common/stores/chat/store-chats';
import { useBrowserTranslationWarning } from '~/common/components/useIsBrowserTranslating';
@@ -192,9 +192,9 @@ export function ChatMessageList(props: {
setSelectedMessages(new Set());
}, [props.conversationHandler, selectedMessages]);
- useGlobalShortcut(props.isMessageSelectionMode && ShortcutKeyName.Esc, false, false, false, () => {
+ useGlobalShortcuts([[props.isMessageSelectionMode && ShortcutKeyName.Esc, false, false, false, () => {
props.setIsMessageSelectionMode(false);
- });
+ }]]);
// text-diff functionality: only diff the last complete message, and they're similar in size
diff --git a/src/apps/chat/components/composer/Composer.tsx b/src/apps/chat/components/composer/Composer.tsx
index 14680bfb6..835644fc2 100644
--- a/src/apps/chat/components/composer/Composer.tsx
+++ b/src/apps/chat/components/composer/Composer.tsx
@@ -41,7 +41,7 @@ import { supportsScreenCapture } from '~/common/util/screenCaptureUtils';
import { useAppStateStore } from '~/common/state/store-appstate';
import { useChatOverlayStore } from '~/common/chats/store-chat-overlay';
import { useDebouncer } from '~/common/components/useDebouncer';
-import { useGlobalShortcut } from '~/common/components/useGlobalShortcut';
+import { useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
import { useUICounter, useUIPreferencesStore } from '~/common/state/store-ui';
import { useUXLabsStore } from '~/common/state/store-ux-labs';
@@ -420,7 +420,7 @@ export function Composer(props: {
const { isSpeechEnabled, isSpeechError, isRecordingAudio, isRecordingSpeech, toggleRecording } =
useSpeechRecognition(onSpeechResultCallback, chatMicTimeoutMs || 2000);
- useGlobalShortcut('m', true, false, false, toggleRecording);
+ useGlobalShortcuts([['m', true, false, false, toggleRecording]]);
const micIsRunning = !!speechInterimResult;
const micContinuationTrigger = micContinuation && !micIsRunning && !assistantAbortible && !isSpeechError;
@@ -472,7 +472,7 @@ export function Composer(props: {
}
}, [attachAppendFile]);
- useGlobalShortcut(supportsClipboardRead ? 'v' : false, true, true, false, attachAppendClipboardItems);
+ useGlobalShortcuts([[supportsClipboardRead ? 'v' : false, true, true, false, attachAppendClipboardItems]]);
// Attachments Down
diff --git a/src/apps/chat/components/layout-bar/ChatBarAltBeam.tsx b/src/apps/chat/components/layout-bar/ChatBarAltBeam.tsx
index 03cf3039f..f0c066487 100644
--- a/src/apps/chat/components/layout-bar/ChatBarAltBeam.tsx
+++ b/src/apps/chat/components/layout-bar/ChatBarAltBeam.tsx
@@ -10,7 +10,7 @@ import { BeamStoreApi, useBeamStore } from '~/modules/beam/store-beam.hooks';
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
import { GoodTooltip } from '~/common/components/GoodTooltip';
import { KeyStroke } from '~/common/components/KeyStroke';
-import { ShortcutKeyName, useGlobalShortcut } from '~/common/components/useGlobalShortcut';
+import { ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
import { animationBackgroundBeamGather, animationColorBeamScatterINV, animationEnterBelow } from '~/common/util/animUtils';
@@ -59,7 +59,7 @@ export function ChatBarAltBeam(props: {
// intercept esc this beam is focused
- useGlobalShortcut(ShortcutKeyName.Esc, false, false, false, handleCloseBeam);
+ useGlobalShortcuts([[ShortcutKeyName.Esc, false, false, false, handleCloseBeam]]);
return (
diff --git a/src/apps/personas/creator/Creator.tsx b/src/apps/personas/creator/Creator.tsx
index 591e7bbb8..570ea02f5 100644
--- a/src/apps/personas/creator/Creator.tsx
+++ b/src/apps/personas/creator/Creator.tsx
@@ -31,12 +31,15 @@ const Prompts: string[] = [
'Compare the draft character sheet with the original transcript, validating its content and ensuring it captures both the speaker’s overt characteristics and the subtler undertones. Omit unknown information, fine-tune any areas that require clarity, have been overlooked, or require more authenticity. Use clear and illustrative examples from the transcript to refine your sheet and offer meaningful, tangible reference points. Your output is a coherent, comprehensive, and nuanced instruction that begins with \'You are a...\' and serves as a go-to guide for an actor recreating the persona.',
];
-const PromptTitles: string[] = [
- 'Common: Creator System Prompt',
- 'Analyze the transcript',
- 'Define the character',
- 'Cross the t\'s',
-];
+const getTitlesForTab = (selectedTab: number): string[] => {
+ const analyzeSubject: string = selectedTab ? 'text' : 'transcript';
+ return [
+ 'Common: Creator System Prompt',
+ `Analyze the ${analyzeSubject}`,
+ 'Define the character',
+ 'Cross the t\'s',
+ ];
+};
// chain to convert a text input string (e.g. youtube transcript) into a persona prompt
function createChain(instructions: string[], titles: string[]): LLMChainStep[] {
@@ -99,16 +102,18 @@ export function Creator(props: { display: boolean }) {
// editable prompts
+ const promptTitles = React.useMemo(() => getTitlesForTab(selectedTab), [selectedTab]);
+
const {
strings: editedInstructions, stringEditors: instructionEditors,
- } = useFormEditTextArray(Prompts, PromptTitles);
+ } = useFormEditTextArray(Prompts, promptTitles);
const { steps: creationChainSteps, id: chainId } = React.useMemo(() => {
return {
- steps: createChain(editedInstructions, PromptTitles),
+ steps: createChain(editedInstructions, promptTitles),
id: agiUuid('persona-creator-chain'),
};
- }, [editedInstructions]);
+ }, [editedInstructions, promptTitles]);
const llmLabel = personaLlm?.label || undefined;
const savePersona = React.useCallback((personaPrompt: string, inputText: string) => {
diff --git a/src/apps/settings-modal/ShortcutsModal.tsx b/src/apps/settings-modal/ShortcutsModal.tsx
index 27dec4d98..78572aecd 100644
--- a/src/apps/settings-modal/ShortcutsModal.tsx
+++ b/src/apps/settings-modal/ShortcutsModal.tsx
@@ -3,37 +3,38 @@ import * as React from 'react';
import { AutoBlocksRenderer } from '~/modules/blocks/AutoBlocksRenderer';
import { GoodModal } from '~/common/components/GoodModal';
+import { isMacUser } from '~/common/util/pwaUtils';
import { platformAwareKeystrokes } from '~/common/components/KeyStroke';
import { useIsMobile } from '~/common/components/useMatchMedia';
const shortcutsMd = platformAwareKeystrokes(`
-| Shortcut | Description |
-|---------------------|-------------------------------------------------|
-| **Edit** | |
-| Shift + Enter | Newline |
-| Alt + Enter | Append (no response) |
-| Ctrl + Shift + B | **Beam** last message |
-| Ctrl + Shift + R | **Regenerate** last message |
-| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) |
-| Ctrl + M | Microphone (voice typing) |
-| **Chats** | |
-| Ctrl + O | Open Chat File ... |
-| Ctrl + S | Save Chat File ... |
-| Ctrl + Alt + N | **New** chat |
-| Ctrl + Alt + X | **Reset** chat |
-| Ctrl + Alt + D | **Delete** chat |
-| Ctrl + Alt + B | **Branch** chat |
-| Ctrl + Alt + Left | **Previous** chat (in history) |
-| Ctrl + Alt + Right | **Next** chat (in history) |
-| **Settings** | |
-| Ctrl + Shift + P | ⚙️ Preferences |
-| Ctrl + Shift + M | 🧠 Models |
-| Ctrl + Shift + O | 💬 Options (current Chat Model) |
-| Ctrl + Shift + + | Increase Text Size |
-| Ctrl + Shift + - | Decrease Text Size |
-| Ctrl + Shift + ? | Shortcuts |
+| Shortcut | Description |
+|-----------------------------------------|-------------------------------------------------|
+| **Edit** | |
+| Shift + Enter | Newline |
+| Alt + Enter | Append (no response) |
+| Ctrl + Shift + B | **Beam** last message |
+| Ctrl + Shift + R | **Regenerate** last message |
+| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) |
+| Ctrl + M | Microphone (voice typing) |
+| **Chats** | |
+| Ctrl + O | Open Chat ... |
+| Ctrl + S | Save Chat ... |
+| Ctrl + ${isMacUser ? '' : 'Alt +'} N | **New** chat |
+| Ctrl + ${isMacUser ? '' : 'Alt +'} X | **Reset** chat |
+| Ctrl + ${isMacUser ? '' : 'Alt +'} D | **Delete** chat |
+| Ctrl + ${isMacUser ? '' : 'Alt +'} B | **Branch** chat |
+| Ctrl + Alt + Left | **Previous** chat (in history) |
+| Ctrl + Alt + Right | **Next** chat (in history) |
+| **Settings** | |
+| Ctrl + Shift + P | ⚙️ Preferences |
+| Ctrl + Shift + M | 🧠 Models |
+| Ctrl + Shift + O | 💬 Options (current Chat Model) |
+| Ctrl + Shift + + | Increase Text Size |
+| Ctrl + Shift + - | Decrease Text Size |
+| Ctrl + Shift + ${isMacUser ? '/' : '?'} | Shortcuts |
`).trim();
@@ -55,4 +56,4 @@ export function ShortcutsModal(props: { onClose: () => void }) {
/>
);
-}
\ No newline at end of file
+}
diff --git a/src/common/components/ExplainerCarousel.tsx b/src/common/components/ExplainerCarousel.tsx
index 05b2b33d9..6aa405052 100644
--- a/src/common/components/ExplainerCarousel.tsx
+++ b/src/common/components/ExplainerCarousel.tsx
@@ -12,7 +12,7 @@ import { AutoBlocksRenderer } from '~/modules/blocks/AutoBlocksRenderer';
import { AgiSquircleIcon } from '~/common/components/icons/AgiSquircleIcon';
import { ChatBeamIcon } from '~/common/components/icons/ChatBeamIcon';
-import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
+import { GlobalShortcutDefinition, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
import { hasGoogleAnalytics } from '~/common/components/GoogleAnalytics';
import { useIsMobile } from '~/common/components/useMatchMedia';
import { animationTextShadowLimey } from '~/common/util/animUtils';
@@ -159,7 +159,7 @@ export function ExplainerCarousel(props: {
}, [props.explainerId]);
- const shortcuts = React.useMemo((): GlobalShortcutItem[] => [
+ const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [
[ShortcutKeyName.Left, false, false, false, handlePrevPage],
[ShortcutKeyName.Right, false, false, false, handleNextPage],
], [handleNextPage, handlePrevPage]);
diff --git a/src/common/components/KeyStroke.tsx b/src/common/components/KeyStroke.tsx
index 517692141..5c2a13f4a 100644
--- a/src/common/components/KeyStroke.tsx
+++ b/src/common/components/KeyStroke.tsx
@@ -10,7 +10,7 @@ import { isMacUser } from '~/common/util/pwaUtils';
export function platformAwareKeystrokes(text: string) {
return isMacUser
? text
- .replaceAll('Ctrl', '⌘' /* Command */)
+ .replaceAll('Ctrl', '⌃' /* Control */)
.replaceAll('Alt', '⌥' /* Option */)
.replaceAll('Shift', '⇧')
// Optional: Replace "Enter" with "Return" if you want to align with Mac keyboard labeling
diff --git a/src/common/components/icons/vendors/DeepseekIcon.tsx b/src/common/components/icons/vendors/DeepseekIcon.tsx
new file mode 100644
index 000000000..811410b75
--- /dev/null
+++ b/src/common/components/icons/vendors/DeepseekIcon.tsx
@@ -0,0 +1,9 @@
+import * as React from 'react';
+
+import { SvgIcon, SvgIconProps } from '@mui/joy';
+
+export function DeepseekIcon(props: SvgIconProps) {
+ return
+
+ ;
+}
diff --git a/src/common/components/useGlobalShortcut.ts b/src/common/components/useGlobalShortcut.ts
deleted file mode 100644
index b31641df5..000000000
--- a/src/common/components/useGlobalShortcut.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import * as React from 'react';
-
-export const ShortcutKeyName = {
- Esc: 'Escape',
- Left: 'ArrowLeft',
- Right: 'ArrowRight',
-};
-
-
-/**
- * Registers a global keyboard shortcut (if not undefined) to activate a callback.
- *
- * @param shortcutKey If undefined, the shortcut will not be registered.
- * @param useCtrl If true, the Ctrl key must be pressed for the shortcut to be activated.
- * @param useShift If true, the Shift key must be pressed for the shortcut to be activated.
- * @param useAlt If true, the Alt key must be pressed for the shortcut to be activated.
- * @param callback Make sure this is a memoized callback, otherwise the effect will be re-registered every time.
- */
-export const useGlobalShortcut = (shortcutKey: string | false, useCtrl: boolean, useShift: boolean, useAlt: boolean, callback: () => void) => {
- React.useEffect(() => {
- if (!shortcutKey) return;
- const lcShortcut = shortcutKey.toLowerCase();
- const handleKeyDown = (event: KeyboardEvent) => {
- const isCtrlOrCmd = (event.ctrlKey && !event.metaKey) || (event.metaKey && !event.ctrlKey);
- if (
- (useCtrl === isCtrlOrCmd) &&
- (useShift === event.shiftKey) &&
- (useAlt === event.altKey) &&
- event.key.toLowerCase() === lcShortcut
- ) {
- event.preventDefault();
- event.stopPropagation();
- callback();
- }
- };
- window.addEventListener('keydown', handleKeyDown);
- return () => window.removeEventListener('keydown', handleKeyDown);
- }, [callback, shortcutKey, useAlt, useCtrl, useShift]);
-};
-
-
-export type GlobalShortcutItem = [key: string, ctrl: boolean, shift: boolean, alt: boolean, action: () => void];
-
-
-/**
- * Registers multiple global keyboard shortcuts to activate callbacks.
- *
- * @param shortcuts An array of shortcut objects.
- */
-export const useGlobalShortcuts = (shortcuts: GlobalShortcutItem[]) => {
- React.useEffect(() => {
- const handleKeyDown = (event: KeyboardEvent) => {
- for (const [key, useCtrl, useShift, useAlt, action] of shortcuts) {
- const isCtrlOrCmd = (event.ctrlKey && !event.metaKey) || (event.metaKey && !event.ctrlKey);
- if (
- key &&
- (useCtrl === isCtrlOrCmd) &&
- (useShift === event.shiftKey) &&
- (useAlt === event.altKey) &&
- event.key.toLowerCase() === key.toLowerCase()
- ) {
- event.preventDefault();
- event.stopPropagation();
- action();
- break;
- }
- }
- };
- window.addEventListener('keydown', handleKeyDown);
- return () => window.removeEventListener('keydown', handleKeyDown);
- }, [shortcuts]);
-};
\ No newline at end of file
diff --git a/src/common/components/useGlobalShortcuts.ts b/src/common/components/useGlobalShortcuts.ts
new file mode 100644
index 000000000..b94b8ebb4
--- /dev/null
+++ b/src/common/components/useGlobalShortcuts.ts
@@ -0,0 +1,44 @@
+import * as React from 'react';
+
+import { isMacUser } from '../util/pwaUtils';
+
+
+export const ShortcutKeyName = {
+ Esc: 'Escape',
+ Left: 'ArrowLeft',
+ Right: 'ArrowRight',
+};
+
+export type GlobalShortcutDefinition = [key: string | false, useCtrl: boolean, useShift: boolean, useAltForNonMac: boolean, action: () => void];
+
+/**
+ * Registers multiple global keyboard shortcuts -> function mappings.
+ *
+ * Important notes below:
+ * - [MAC only] the Alt key is ignored even if defined in the shortcut
+ * - [MAC only] are not using the command key at the moment, as it interfered with browser shortcuts
+ * - stabilize the shortcuts definition (e.g. React.useMemo()) to avoid re-registering the shortcuts at every render
+ *
+ */
+export const useGlobalShortcuts = (shortcuts: GlobalShortcutDefinition[]) => {
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ for (const [key, useCtrl, useShift, useAltForNonMac, action] of shortcuts) {
+ if (
+ key &&
+ (useCtrl === event.ctrlKey) &&
+ (useShift === event.shiftKey) &&
+ (isMacUser /* Mac users won't need the Alt keys */ || useAltForNonMac === event.altKey) &&
+ event.key.toLowerCase() === key.toLowerCase()
+ ) {
+ event.preventDefault();
+ event.stopPropagation();
+ action();
+ break;
+ }
+ }
+ };
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [shortcuts]);
+};
diff --git a/src/common/layout/optima/useOptimaLayout.tsx b/src/common/layout/optima/useOptimaLayout.tsx
index 89414394d..d2802a5f6 100644
--- a/src/common/layout/optima/useOptimaLayout.tsx
+++ b/src/common/layout/optima/useOptimaLayout.tsx
@@ -2,7 +2,8 @@ import * as React from 'react';
import type { DLLMId } from '~/modules/llms/store-llms';
-import { GlobalShortcutItem, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
+import { GlobalShortcutDefinition, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
+import { isMacUser } from '~/common/util/pwaUtils';
const DEBUG_OPTIMA_LAYOUT_PLUGGING = false;
@@ -114,8 +115,8 @@ export function OptimaLayoutProvider(props: { children: React.ReactNode }) {
// global shortcuts for Optima
- const shortcuts = React.useMemo((): GlobalShortcutItem[] => [
- ['?', true, true, false, actions.openShortcuts],
+ const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [
+ [isMacUser ? '/' : '?', true, true, false, actions.openShortcuts],
['m', true, true, false, actions.openModelsSetup],
['p', true, true, false, actions.openPreferencesTab],
], [actions]);
diff --git a/src/modules/backend/backend.router.ts b/src/modules/backend/backend.router.ts
index ad83a90f7..f6f4e6aba 100644
--- a/src/modules/backend/backend.router.ts
+++ b/src/modules/backend/backend.router.ts
@@ -49,6 +49,7 @@ export const backendRouter = createTRPCRouter({
hasImagingProdia: !!env.PRODIA_API_KEY,
hasLlmAnthropic: !!env.ANTHROPIC_API_KEY,
hasLlmAzureOpenAI: !!env.AZURE_OPENAI_API_KEY && !!env.AZURE_OPENAI_API_ENDPOINT,
+ hasLlmDeepseek: !!env.DEEPSEEK_API_KEY,
hasLlmGemini: !!env.GEMINI_API_KEY,
hasLlmGroq: !!env.GROQ_API_KEY,
hasLlmLocalAIHost: !!env.LOCALAI_API_HOST,
diff --git a/src/modules/backend/store-backend-capabilities.ts b/src/modules/backend/store-backend-capabilities.ts
index 0f6ee1f3a..2824eb7ff 100644
--- a/src/modules/backend/store-backend-capabilities.ts
+++ b/src/modules/backend/store-backend-capabilities.ts
@@ -13,6 +13,7 @@ export interface BackendCapabilities {
hasImagingProdia: boolean;
hasLlmAnthropic: boolean;
hasLlmAzureOpenAI: boolean;
+ hasLlmDeepseek: boolean;
hasLlmGemini: boolean;
hasLlmGroq: boolean;
hasLlmLocalAIHost: boolean;
@@ -42,6 +43,7 @@ const useBackendCapabilitiesStore = create()(
hasImagingProdia: false,
hasLlmAnthropic: false,
hasLlmAzureOpenAI: false,
+ hasLlmDeepseek: false,
hasLlmGemini: false,
hasLlmGroq: false,
hasLlmLocalAIHost: false,
diff --git a/src/modules/llms/server/llm.server.streaming.ts b/src/modules/llms/server/llm.server.streaming.ts
index aac651e34..b99019dbf 100644
--- a/src/modules/llms/server/llm.server.streaming.ts
+++ b/src/modules/llms/server/llm.server.streaming.ts
@@ -526,6 +526,7 @@ function _prepareRequestData({ access, model, history, context: _context }: Chat
};
case 'azure':
+ case 'deepseek':
case 'groq':
case 'lmstudio':
case 'localai':
diff --git a/src/modules/llms/server/openai/models.data.ts b/src/modules/llms/server/openai/models.data.ts
index 0cf234174..a8a5d57ee 100644
--- a/src/modules/llms/server/openai/models.data.ts
+++ b/src/modules/llms/server/openai/models.data.ts
@@ -321,6 +321,49 @@ export function azureModelToModelDescription(azureDeploymentRef: string, openAIM
}
+// [Deepseek AI]
+const _knownDeepseekChatModels: ManualMappings = [
+ // [Models and Pricing](https://platform.deepseek.com/api-docs/pricing)
+ // [List Models](https://platform.deepseek.com/api-docs/api/list-models)
+ {
+ idPrefix: 'deepseek-chat',
+ label: 'Deepseek Chat V2',
+ description: 'Good at general tasks, 128K context length',
+ contextWindow: 128000,
+ interfaces: [LLM_IF_OAI_Chat],
+ maxCompletionTokens: 4096,
+ pricing: {
+ chatIn: 0.14,
+ chatOut: 0.28,
+ },
+ },
+ {
+ idPrefix: 'deepseek-coder',
+ label: 'Deepseek Coder V2',
+ description: 'Good at coding and math tasks, 128K context length',
+ contextWindow: 128000,
+ interfaces: [LLM_IF_OAI_Chat],
+ maxCompletionTokens: 4096,
+ pricing: {
+ chatIn: 0.14,
+ chatOut: 0.28,
+ },
+ },
+];
+
+export function deepseekModelToModelDescription(deepseekModelId: string): ModelDescriptionSchema {
+ return fromManualMapping(_knownDeepseekChatModels, deepseekModelId, undefined, undefined, {
+ idPrefix: deepseekModelId,
+ label: deepseekModelId.replaceAll(/[_-]/g, ' '),
+ description: 'New Deepseek Model',
+ contextWindow: 128000,
+ maxCompletionTokens: 4096,
+ interfaces: [LLM_IF_OAI_Chat], // assume..
+ hidden: true,
+ });
+}
+
+
// [LM Studio]
export function lmStudioModelToModelDescription(modelId: string): ModelDescriptionSchema {
diff --git a/src/modules/llms/server/openai/openai.router.ts b/src/modules/llms/server/openai/openai.router.ts
index 38585f6e7..64763f47d 100644
--- a/src/modules/llms/server/openai/openai.router.ts
+++ b/src/modules/llms/server/openai/openai.router.ts
@@ -11,7 +11,7 @@ import { Brand } from '~/common/app.config';
import { fixupHost } from '~/common/util/urlUtils';
import { OpenAIWire, WireOpenAICreateImageOutput, wireOpenAICreateImageOutputSchema, WireOpenAICreateImageRequest } from './openai.wiretypes';
-import { azureModelToModelDescription, groqModelSortFn, groqModelToModelDescription, lmStudioModelToModelDescription, localAIModelToModelDescription, mistralModelsSort, mistralModelToModelDescription, oobaboogaModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openRouterModelFamilySortFn, openRouterModelToModelDescription, perplexityAIModelDescriptions, perplexityAIModelSort, togetherAIModelsToModelDescriptions } from './models.data';
+import { azureModelToModelDescription, deepseekModelToModelDescription, groqModelSortFn, groqModelToModelDescription, lmStudioModelToModelDescription, localAIModelToModelDescription, mistralModelsSort, mistralModelToModelDescription, oobaboogaModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openRouterModelFamilySortFn, openRouterModelToModelDescription, perplexityAIModelDescriptions, perplexityAIModelSort, togetherAIModelsToModelDescriptions } from './models.data';
import { llmsChatGenerateWithFunctionsOutputSchema, llmsGenerateContextSchema, llmsListModelsOutputSchema, ModelDescriptionSchema } from '../llm.server.types';
import { wilreLocalAIModelsApplyOutputSchema, wireLocalAIModelsAvailableOutputSchema, wireLocalAIModelsListOutputSchema } from './localai.wiretypes';
@@ -21,7 +21,7 @@ const ABERRATION_FIXUP_SQUASH = '\n\n\n---\n\n\n';
const openAIDialects = z.enum([
- 'azure', 'groq', 'lmstudio', 'localai', 'mistral', 'oobabooga', 'openai', 'openrouter', 'perplexity', 'togetherai',
+ 'azure', 'deepseek', 'groq', 'lmstudio', 'localai', 'mistral', 'oobabooga', 'openai', 'openrouter', 'perplexity', 'togetherai',
]);
type OpenAIDialects = z.infer;
@@ -173,6 +173,11 @@ export const llmOpenAIRouter = createTRPCRouter({
// every dialect has a different way to enumerate models - we execute the mapping on the server side
switch (access.dialect) {
+ case 'deepseek':
+ models = openAIModels
+ .map(({ id }) => deepseekModelToModelDescription(id));
+ break;
+
case 'groq':
models = openAIModels
.map(groqModelToModelDescription)
@@ -420,6 +425,7 @@ export const llmOpenAIRouter = createTRPCRouter({
const DEFAULT_HELICONE_OPENAI_HOST = 'oai.hconeai.com';
+const DEFAULT_DEEPSEEK_HOST = 'https://api.deepseek.com';
const DEFAULT_GROQ_HOST = 'https://api.groq.com/openai';
const DEFAULT_LOCALAI_HOST = 'http://127.0.0.1:8080';
const DEFAULT_MISTRAL_HOST = 'https://api.mistral.ai';
@@ -456,6 +462,22 @@ export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | nu
};
+ case 'deepseek':
+ // https://platform.deepseek.com/api-docs/
+ const deepseekKey = access.oaiKey || env.DEEPSEEK_API_KEY || '';
+ const deepseekHost = fixupHost(access.oaiHost || DEFAULT_DEEPSEEK_HOST, apiPath);
+ if (!deepseekKey || !deepseekHost)
+ throw new Error('Missing Deepseek API Key or Host. Add it on the UI (Models Setup) or server side (your deployment).');
+
+ return {
+ headers: {
+ 'Authorization': `Bearer ${deepseekKey}`,
+ 'Content-Type': 'application/json',
+ },
+ url: deepseekHost + apiPath,
+ };
+
+
case 'lmstudio':
case 'oobabooga':
case 'openai':
diff --git a/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx b/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx
new file mode 100644
index 000000000..a117d4d6e
--- /dev/null
+++ b/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx
@@ -0,0 +1,61 @@
+import * as React from 'react';
+
+import { AlreadySet } from '~/common/components/AlreadySet';
+import { FormInputKey } from '~/common/components/forms/FormInputKey';
+import { InlineError } from '~/common/components/InlineError';
+import { Link } from '~/common/components/Link';
+import { SetupFormRefetchButton } from '~/common/components/forms/SetupFormRefetchButton';
+import { useToggleableBoolean } from '~/common/util/useToggleableBoolean';
+
+import { DModelSourceId } from '../../store-llms';
+import { useLlmUpdateModels } from '../../llm.client.hooks';
+import { useSourceSetup } from '../useSourceSetup';
+
+import { ModelVendorDeepseek } from './deepseekai.vendor';
+
+
+const DEEPSEEK_REG_LINK = 'https://platform.deepseek.com/api_keys';
+
+
+export function DeepseekAISourceSetup(props: { sourceId: DModelSourceId }) {
+
+ // state
+ const advanced = useToggleableBoolean();
+
+ // external state
+ const {
+ source, sourceHasLLMs, access,
+ sourceSetupValid, hasNoBackendCap: needsUserKey, updateSetup,
+ } = useSourceSetup(props.sourceId, ModelVendorDeepseek);
+
+ // derived state
+ const { oaiKey: deepseekKey } = access;
+
+ // validate if url is a well formed proper url with zod
+ const shallFetchSucceed = !needsUserKey || (!!deepseekKey && sourceSetupValid);
+ const showKeyError = !!deepseekKey && !sourceSetupValid;
+
+ // fetch models
+ const { isFetching, refetch, isError, error } =
+ useLlmUpdateModels(!sourceHasLLMs && shallFetchSucceed, source);
+
+
+ return <>
+
+ {needsUserKey
+ ? !deepseekKey && request Key
+ : }
+ >}
+ value={deepseekKey} onChange={value => updateSetup({ deepseekKey: value })}
+ required={needsUserKey} isError={showKeyError}
+ placeholder='...'
+ />
+
+
+
+ {isError && }
+
+ >;
+}
diff --git a/src/modules/llms/vendors/deepseek/deepseekai.vendor.ts b/src/modules/llms/vendors/deepseek/deepseekai.vendor.ts
new file mode 100644
index 000000000..779e61fce
--- /dev/null
+++ b/src/modules/llms/vendors/deepseek/deepseekai.vendor.ts
@@ -0,0 +1,49 @@
+import { DeepseekIcon } from '~/common/components/icons/vendors/DeepseekIcon';
+
+import type { IModelVendor } from '../IModelVendor';
+import type { OpenAIAccessSchema } from '../../server/openai/openai.router';
+
+import { LLMOptionsOpenAI, ModelVendorOpenAI } from '../openai/openai.vendor';
+import { OpenAILLMOptions } from '../openai/OpenAILLMOptions';
+
+import { DeepseekAISourceSetup } from './DeepseekAISourceSetup';
+
+
+export interface SourceSetupDeepseek {
+ deepseekKey: string;
+}
+
+export const ModelVendorDeepseek: IModelVendor = {
+ id: 'deepseek',
+ name: 'Deepseek',
+ rank: 19,
+ location: 'cloud',
+ instanceLimit: 1,
+ hasBackendCapKey: 'hasLlmDeepseek',
+
+ // components
+ Icon: DeepseekIcon,
+ SourceSetupComponent: DeepseekAISourceSetup,
+ LLMOptionsComponent: OpenAILLMOptions,
+
+ // functions
+ initializeSetup: () => ({
+ deepseekKey: '',
+ }),
+ validateSetup: (setup) => {
+ return setup.deepseekKey?.length >= 35;
+ },
+ getTransportAccess: (partialSetup) => ({
+ dialect: 'deepseek',
+ oaiKey: partialSetup?.deepseekKey || '',
+ oaiOrg: '',
+ oaiHost: '',
+ heliKey: '',
+ moderationCheck: false,
+ }),
+
+ // OpenAI transport ('Deepseek' dialect in 'access')
+ rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow,
+ rpcChatGenerateOrThrow: ModelVendorOpenAI.rpcChatGenerateOrThrow,
+ streamingChatGenerateOrThrow: ModelVendorOpenAI.streamingChatGenerateOrThrow,
+};
diff --git a/src/modules/llms/vendors/vendors.registry.ts b/src/modules/llms/vendors/vendors.registry.ts
index 0193718e4..f4353919f 100644
--- a/src/modules/llms/vendors/vendors.registry.ts
+++ b/src/modules/llms/vendors/vendors.registry.ts
@@ -14,6 +14,7 @@ import { ModelVendorTogetherAI } from './togetherai/togetherai.vendor';
import type { IModelVendor } from './IModelVendor';
import { DLLMId, DModelSource, DModelSourceId, findLLMOrThrow, findSourceOrThrow } from '../store-llms';
+import { ModelVendorDeepseek } from './deepseek/deepseekai.vendor';
export type ModelVendorId =
| 'anthropic'
@@ -28,7 +29,8 @@ export type ModelVendorId =
| 'openai'
| 'openrouter'
| 'perplexity'
- | 'togetherai';
+ | 'togetherai'
+ | 'deepseek';
/** Global: Vendor Instances Registry **/
const MODEL_VENDOR_REGISTRY: Record = {
@@ -45,6 +47,7 @@ const MODEL_VENDOR_REGISTRY: Record = {
openrouter: ModelVendorOpenRouter,
perplexity: ModelVendorPerplexity,
togetherai: ModelVendorTogetherAI,
+ deepseek: ModelVendorDeepseek,
} as Record;
const MODEL_VENDOR_DEFAULT: ModelVendorId = 'openai';
diff --git a/src/modules/t2i/dalle/DallESettings.tsx b/src/modules/t2i/dalle/DallESettings.tsx
index d49e7b4a7..8f84c31b5 100644
--- a/src/modules/t2i/dalle/DallESettings.tsx
+++ b/src/modules/t2i/dalle/DallESettings.tsx
@@ -57,7 +57,7 @@ export function DallESettings() {
{isDallE3 &&
>;
-}
\ No newline at end of file
+}
diff --git a/src/server/env.mjs b/src/server/env.mjs
index 8c75b7ec2..00fd86132 100644
--- a/src/server/env.mjs
+++ b/src/server/env.mjs
@@ -53,6 +53,9 @@ export const env = createEnv({
// LLM: Toghether AI
TOGETHERAI_API_KEY: z.string().optional(),
+ // LLM: Deepseek AI
+ DEEPSEEK_API_KEY: z.string().optional(),
+
// Helicone - works on both OpenAI and Anthropic vendors
HELICONE_API_KEY: z.string().optional(),