= {
granted: t('settings.general.notificationsStatusGranted'),
denied: t('settings.general.notificationsStatusDenied'),
@@ -2088,6 +2103,31 @@ function GeneralSettings() {
+
+
{t('settings.general.chatSendBehaviorTitle')}
+
{t('settings.general.chatSendBehaviorDescription')}
+
+ {CHAT_SEND_BEHAVIORS.map((option) => (
+
+ ))}
+
+
+
{uiZoomSection}
diff --git a/desktop/src/stores/settingsStore.test.ts b/desktop/src/stores/settingsStore.test.ts
index 3dc5931b..598b769d 100644
--- a/desktop/src/stores/settingsStore.test.ts
+++ b/desktop/src/stores/settingsStore.test.ts
@@ -279,6 +279,56 @@ describe('settingsStore network persistence', () => {
},
})
})
+
+ it('persists the chat send behavior preference and normalizes invalid values', async () => {
+ const updateUser = vi.fn().mockResolvedValue({})
+ vi.doMock('../api/settings', () => ({
+ settingsApi: {
+ getUser: vi.fn().mockResolvedValue({
+ chatSendBehavior: 'unexpected',
+ }),
+ updateUser,
+ getPermissionMode: vi.fn().mockResolvedValue({ mode: 'default' }),
+ setPermissionMode: vi.fn(),
+ getCliLauncherStatus: vi.fn(),
+ },
+ }))
+ vi.doMock('../api/models', () => ({
+ modelsApi: {
+ list: vi.fn().mockResolvedValue({ models: [] }),
+ getCurrent: vi.fn().mockResolvedValue({ model: null }),
+ setCurrent: vi.fn(),
+ getEffort: vi.fn().mockResolvedValue({ level: 'medium' }),
+ setEffort: vi.fn(),
+ },
+ }))
+ vi.doMock('../api/h5Access', () => ({
+ h5AccessApi: {
+ get: vi.fn().mockResolvedValue({
+ settings: {
+ enabled: false,
+ tokenPreview: null,
+ allowedOrigins: [],
+ publicBaseUrl: null,
+ },
+ }),
+ enable: vi.fn(),
+ disable: vi.fn(),
+ regenerate: vi.fn(),
+ update: vi.fn(),
+ },
+ }))
+
+ const { useSettingsStore } = await import('./settingsStore')
+
+ await useSettingsStore.getState().fetchAll()
+ expect(useSettingsStore.getState().chatSendBehavior).toBe('enter')
+
+ await useSettingsStore.getState().setChatSendBehavior('modifierEnter')
+
+ expect(useSettingsStore.getState().chatSendBehavior).toBe('modifierEnter')
+ expect(updateUser).toHaveBeenCalledWith({ chatSendBehavior: 'modifierEnter' })
+ })
})
describe('settingsStore app mode', () => {
diff --git a/desktop/src/stores/settingsStore.ts b/desktop/src/stores/settingsStore.ts
index 687c701b..e7edf80c 100644
--- a/desktop/src/stores/settingsStore.ts
+++ b/desktop/src/stores/settingsStore.ts
@@ -7,6 +7,7 @@ import {
isThemeMode,
type AppMode,
type AppModeConfig,
+ type ChatSendBehavior,
type DesktopTerminalSettings,
type DesktopTerminalStartupShell,
type H5AccessDiagnostics,
@@ -57,6 +58,7 @@ type SettingsStore = {
activeProviderName: string | null
locale: Locale
theme: ThemeMode
+ chatSendBehavior: ChatSendBehavior
skipWebFetchPreflight: boolean
desktopNotificationsEnabled: boolean
desktopTerminal: DesktopTerminalSettings
@@ -82,6 +84,7 @@ type SettingsStore = {
setThinkingEnabled: (enabled: boolean) => Promise
setLocale: (locale: Locale) => void
setTheme: (theme: ThemeMode) => Promise
+ setChatSendBehavior: (behavior: ChatSendBehavior) => Promise
setSkipWebFetchPreflight: (enabled: boolean) => Promise
setDesktopNotificationsEnabled: (enabled: boolean) => Promise
setDesktopTerminal: (settings: DesktopTerminalSettings) => Promise
@@ -139,6 +142,7 @@ export const useSettingsStore = create((set, get) => ({
activeProviderName: null,
locale: getStoredLocale(),
theme: useUIStore.getState().theme,
+ chatSendBehavior: 'enter',
skipWebFetchPreflight: true,
desktopNotificationsEnabled: false,
desktopTerminal: DEFAULT_DESKTOP_TERMINAL_SETTINGS,
@@ -189,6 +193,7 @@ export const useSettingsStore = create((set, get) => ({
effortLevel: level,
thinkingEnabled: userSettings.alwaysThinkingEnabled !== false,
theme,
+ chatSendBehavior: normalizeChatSendBehavior(userSettings.chatSendBehavior),
skipWebFetchPreflight: userSettings.skipWebFetchPreflight !== false,
desktopNotificationsEnabled: userSettings.desktopNotificationsEnabled === true,
desktopTerminal: normalizeDesktopTerminalSettings(userSettings.desktopTerminal),
@@ -272,6 +277,18 @@ export const useSettingsStore = create((set, get) => ({
}
},
+ setChatSendBehavior: async (behavior) => {
+ const prev = get().chatSendBehavior
+ const next = normalizeChatSendBehavior(behavior)
+ set({ chatSendBehavior: next })
+ try {
+ await settingsApi.updateUser({ chatSendBehavior: next })
+ } catch (error) {
+ set({ chatSendBehavior: prev })
+ throw error
+ }
+ },
+
setSkipWebFetchPreflight: async (enabled) => {
const prev = get().skipWebFetchPreflight
set({ skipWebFetchPreflight: enabled })
@@ -466,6 +483,10 @@ function normalizeWebSearchSettings(settings: WebSearchSettings | undefined): We
}
}
+function normalizeChatSendBehavior(value: unknown): ChatSendBehavior {
+ return value === 'modifierEnter' ? 'modifierEnter' : 'enter'
+}
+
function isUpdateProxyMode(value: unknown): value is UpdateProxyMode {
return value === 'system' || value === 'manual'
}
diff --git a/desktop/src/types/settings.ts b/desktop/src/types/settings.ts
index 33b9175d..57061d4c 100644
--- a/desktop/src/types/settings.ts
+++ b/desktop/src/types/settings.ts
@@ -12,6 +12,8 @@ export function isThemeMode(value: unknown): value is ThemeMode {
export type WebSearchMode = 'auto' | 'anthropic' | 'tavily' | 'brave' | 'disabled'
+export type ChatSendBehavior = 'enter' | 'modifierEnter'
+
export type WebSearchSettings = {
mode?: WebSearchMode
tavilyApiKey?: string
@@ -80,6 +82,7 @@ export type UserSettings = {
alwaysThinkingEnabled?: boolean
permissionMode?: PermissionMode
theme?: ThemeMode
+ chatSendBehavior?: ChatSendBehavior
skipWebFetchPreflight?: boolean
desktopNotificationsEnabled?: boolean
webSearch?: WebSearchSettings