refactor(ui): register application shortcuts centrally
This commit is contained in:
@@ -33,11 +33,12 @@ import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore';
|
||||
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useDesktopWindowControlsLayout } from '@/hooks/useDesktopWindowControlsLayout';
|
||||
import { useKeybinds } from '@/hooks/useKeybind';
|
||||
import { ContextUsageDisplay } from '@/components/ui/ContextUsageDisplay';
|
||||
import { WindowsWindowControls } from '@/components/desktop/WindowsWindowControls';
|
||||
import { UpdateDialog } from '@/components/ui/UpdateDialog';
|
||||
import { useDeviceInfo, useTabletStandalonePwaRuntime } from '@/lib/device';
|
||||
import { cn, hasModifier } from '@/lib/utils';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { McpDropdownContent } from '@/components/mcp/McpDropdown';
|
||||
import { McpIcon } from '@/components/icons/McpIcon';
|
||||
import { ProviderLogo } from '@/components/ui/ProviderLogo';
|
||||
@@ -46,7 +47,11 @@ import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
|
||||
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { formatTimeForPreference } from '@/lib/timeFormat';
|
||||
import { eventMatchesShortcut, formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts';
|
||||
import {
|
||||
formatShortcutForDisplay,
|
||||
getEffectiveShortcutCombo,
|
||||
type ShortcutActionId,
|
||||
} from '@/lib/shortcuts';
|
||||
import type { TimeFormatPreference } from '@/stores/useUIStore';
|
||||
import {
|
||||
getAllModelFamilies,
|
||||
@@ -285,7 +290,7 @@ type DesktopServicesMenuProps = {
|
||||
rateLimitGroups: RateLimitGroup[];
|
||||
expandedFamilies: Record<string, string[]>;
|
||||
toggleFamilyExpanded: (providerId: string, familyId: string) => void;
|
||||
shortcutLabel: (actionId: string) => string;
|
||||
shortcutLabel: (actionId: ShortcutActionId) => string;
|
||||
showDevShutdown: boolean;
|
||||
isDevShutdownInFlight: boolean;
|
||||
onDevShutdown: () => Promise<void>;
|
||||
@@ -1935,7 +1940,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
return [];
|
||||
}, [isMobile, showPlanTab, t]);
|
||||
|
||||
const shortcutLabel = React.useCallback((actionId: string) => {
|
||||
const shortcutLabel = React.useCallback((actionId: ShortcutActionId) => {
|
||||
return formatShortcutForDisplay(getEffectiveShortcutCombo(actionId, shortcutOverrides));
|
||||
}, [shortcutOverrides]);
|
||||
|
||||
@@ -2043,82 +2048,53 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
];
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (hasModifier(e) && !e.shiftKey && !e.altKey) {
|
||||
const num = parseInt(e.key, 10);
|
||||
if (num >= 1 && num <= tabs.length) {
|
||||
e.preventDefault();
|
||||
if (isMobile) {
|
||||
blurActiveElement();
|
||||
closeMobileHeaderPanels();
|
||||
}
|
||||
setActiveMainTab(tabs[num - 1].id);
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [blurActiveElement, closeMobileHeaderPanels, isMobile, setActiveMainTab, tabs]);
|
||||
const switchToIndexedTab = (index: number) => {
|
||||
const tab = tabs[index];
|
||||
if (!tab) return false;
|
||||
if (isMobile) {
|
||||
blurActiveElement();
|
||||
closeMobileHeaderPanels();
|
||||
}
|
||||
setActiveMainTab(tab.id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const toggleServicesCombo = getEffectiveShortcutCombo('toggle_services_menu', shortcutOverrides);
|
||||
if (eventMatchesShortcut(e, toggleServicesCombo)) {
|
||||
e.preventDefault();
|
||||
|
||||
if (isDesktopServicesOpen) {
|
||||
setIsDesktopServicesOpen(false);
|
||||
} else {
|
||||
setIsDesktopServicesOpen(true);
|
||||
void refreshCurrentInstanceLabel();
|
||||
if (desktopServicesTab === 'usage' && quotaResults.length === 0) {
|
||||
void fetchAllQuotas();
|
||||
}
|
||||
}
|
||||
useKeybinds({
|
||||
switch_tab_1: () => switchToIndexedTab(0),
|
||||
switch_tab_2: () => switchToIndexedTab(1),
|
||||
switch_tab_3: () => switchToIndexedTab(2),
|
||||
switch_tab_4: () => switchToIndexedTab(3),
|
||||
switch_tab_5: () => switchToIndexedTab(4),
|
||||
switch_tab_6: () => switchToIndexedTab(5),
|
||||
switch_tab_7: () => switchToIndexedTab(6),
|
||||
switch_tab_8: () => switchToIndexedTab(7),
|
||||
switch_tab_9: () => switchToIndexedTab(8),
|
||||
toggle_services_menu: () => {
|
||||
if (isDesktopServicesOpen) {
|
||||
setIsDesktopServicesOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const cycleServicesCombo = getEffectiveShortcutCombo('cycle_services_tab', shortcutOverrides);
|
||||
if (eventMatchesShortcut(e, cycleServicesCombo)) {
|
||||
e.preventDefault();
|
||||
|
||||
const tabValues = servicesTabs.map((tab) => tab.value) as Array<'instance' | 'usage' | 'mcp'>;
|
||||
if (tabValues.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = tabValues.indexOf(desktopServicesTab);
|
||||
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % tabValues.length;
|
||||
const nextTab = tabValues[nextIndex];
|
||||
setDesktopServicesTab(nextTab);
|
||||
setIsDesktopServicesOpen(true);
|
||||
void refreshCurrentInstanceLabel();
|
||||
if (nextTab === 'usage' && quotaResults.length === 0) {
|
||||
void fetchAllQuotas();
|
||||
}
|
||||
return;
|
||||
setIsDesktopServicesOpen(true);
|
||||
void refreshCurrentInstanceLabel();
|
||||
if (desktopServicesTab === 'usage' && quotaResults.length === 0) {
|
||||
void fetchAllQuotas();
|
||||
}
|
||||
|
||||
const toggleContextPlanCombo = getEffectiveShortcutCombo('toggle_context_plan', shortcutOverrides);
|
||||
if (eventMatchesShortcut(e, toggleContextPlanCombo)) {
|
||||
e.preventDefault();
|
||||
handleOpenContextPlan();
|
||||
},
|
||||
cycle_services_tab: () => {
|
||||
const tabValues = servicesTabs.map((tab) => tab.value) as Array<'instance' | 'usage' | 'mcp'>;
|
||||
if (tabValues.length === 0) return false;
|
||||
const currentIndex = tabValues.indexOf(desktopServicesTab);
|
||||
const nextTab = tabValues[currentIndex === -1 ? 0 : (currentIndex + 1) % tabValues.length];
|
||||
setDesktopServicesTab(nextTab);
|
||||
setIsDesktopServicesOpen(true);
|
||||
void refreshCurrentInstanceLabel();
|
||||
if (nextTab === 'usage' && quotaResults.length === 0) {
|
||||
void fetchAllQuotas();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [
|
||||
shortcutOverrides,
|
||||
isDesktopServicesOpen,
|
||||
desktopServicesTab,
|
||||
servicesTabs,
|
||||
quotaResults.length,
|
||||
fetchAllQuotas,
|
||||
refreshCurrentInstanceLabel,
|
||||
handleOpenContextPlan,
|
||||
]);
|
||||
},
|
||||
toggle_context_plan: () => {
|
||||
handleOpenContextPlan();
|
||||
},
|
||||
});
|
||||
|
||||
const renderTab = (tab: TabConfig) => {
|
||||
const isActive = activeMainTab === tab.id;
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { useFileSearchStore } from '@/stores/useFileSearchStore';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { cn, getModifierLabel, getRevealLabelKey, hasModifier } from '@/lib/utils';
|
||||
import { cn, getModifierLabel, getRevealLabelKey } from '@/lib/utils';
|
||||
import { getLanguageFromExtension, getImageMimeType, isBinaryFile, isDrawioFile, isImageFile, isPdfFile, isSvgFile, looksLikeBinaryText } from '@/lib/toolHelpers';
|
||||
import { shouldAllowFileDraftSave, shouldScheduleFileAutosave } from '@/lib/fileEditorAutosave';
|
||||
import { getRuntimeUrlResolver } from '@/lib/runtime-url';
|
||||
@@ -52,6 +52,7 @@ import { getRuntimeApiBaseUrl } from '@/lib/runtime-switch';
|
||||
import { getOutsideFileGrant } from '@/lib/outsideFileGrants';
|
||||
import { DiagramEditor } from '@/components/diagram';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useKeybind, useKeybinds } from '@/hooks/useKeybind';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
@@ -72,7 +73,6 @@ import { ensurePierreThemeRegistered } from '@/lib/shiki/appThemeRegistry';
|
||||
import { getDefaultTheme } from '@/lib/theme/themes';
|
||||
import { isBrowserClientRuntime, openDesktopFileInApp, openDesktopPath } from '@/lib/desktop';
|
||||
import { useOpenInAppsStore } from '@/stores/useOpenInAppsStore';
|
||||
import { eventMatchesShortcut, getEffectiveShortcutCombo } from '@/lib/shortcuts';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { sessionEvents } from '@/lib/sessionEvents';
|
||||
|
||||
@@ -1022,7 +1022,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const setPendingFileNavigation = useUIStore((state) => state.setPendingFileNavigation);
|
||||
const pendingFileFocusPath = useUIStore((state) => state.pendingFileFocusPath);
|
||||
const setPendingFileFocusPath = useUIStore((state) => state.setPendingFileFocusPath);
|
||||
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
|
||||
const fileEditorKeymap = useUIStore((state) => state.fileEditorKeymap);
|
||||
const settingsDefaultFileViewerPreview = useConfigStore((state) => state.settingsDefaultFileViewerPreview);
|
||||
const showMessageTTSButtons = useConfigStore((state) => state.showMessageTTSButtons);
|
||||
@@ -1767,35 +1766,28 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
setAutoSaveStatus('idle');
|
||||
}, [selectedFile?.path]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!hasModifier(e)) {
|
||||
return;
|
||||
}
|
||||
useKeybinds({
|
||||
save_file: (event) => {
|
||||
if (!(event.target instanceof Node) || !editorWrapperRef.current?.contains(event.target)) return false;
|
||||
|
||||
if (e.key.toLowerCase() === 's') {
|
||||
e.preventDefault();
|
||||
// Cancel pending auto-save; user wants immediate save
|
||||
if (autoSaveTimerRef.current) {
|
||||
clearTimeout(autoSaveTimerRef.current);
|
||||
autoSaveTimerRef.current = null;
|
||||
}
|
||||
if (!isSaving) {
|
||||
void saveDraft().then((saved) => {
|
||||
if (!saved) return;
|
||||
setAutoSaveStatus('saved');
|
||||
setTimeout(() => setAutoSaveStatus('idle'), 2000);
|
||||
});
|
||||
}
|
||||
} else if (e.key.toLowerCase() === 'f') {
|
||||
e.preventDefault();
|
||||
setIsSearchOpen(true);
|
||||
// Cancel pending auto-save because the explicit save should run immediately.
|
||||
if (autoSaveTimerRef.current) {
|
||||
clearTimeout(autoSaveTimerRef.current);
|
||||
autoSaveTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [isSaving, saveDraft]);
|
||||
if (!isSaving) {
|
||||
void saveDraft().then((saved) => {
|
||||
if (!saved) return;
|
||||
setAutoSaveStatus('saved');
|
||||
setTimeout(() => setAutoSaveStatus('idle'), 2000);
|
||||
});
|
||||
}
|
||||
},
|
||||
find_in_file: (event) => {
|
||||
if (!(event.target instanceof Node) || !editorWrapperRef.current?.contains(event.target)) return false;
|
||||
setIsSearchOpen(true);
|
||||
},
|
||||
});
|
||||
|
||||
const loadSelectedFile = React.useCallback(async (node: FileNode) => {
|
||||
const loadId = activeFileLoadIdRef.current + 1;
|
||||
@@ -2908,42 +2900,21 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
};
|
||||
}, [isMobile, nudgeEditorSelectionAboveKeyboard]);
|
||||
|
||||
React.useEffect(() => {
|
||||
useKeybind('open_go_to_line', (event) => {
|
||||
if (!canEdit || textViewMode !== 'edit' || isMobile) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const goToLineCombo = getEffectiveShortcutCombo('open_go_to_line', shortcutOverrides);
|
||||
const target = event.target as Element | null;
|
||||
if (target?.closest('[role="dialog"]')) return false;
|
||||
if (!(target instanceof Node) || !editorWrapperRef.current?.contains(target)) return false;
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
const target = event.target as Element | null;
|
||||
if (target?.closest('[role="dialog"]')) {
|
||||
return;
|
||||
}
|
||||
const isEditorTarget = Boolean(target?.closest('.cm-editor'));
|
||||
const isTypingTarget = Boolean(target?.closest('input, textarea, [contenteditable="true"], [role="textbox"]'));
|
||||
if (isTypingTarget && !isEditorTarget) return false;
|
||||
|
||||
const isEditorTarget = Boolean(target?.closest('.cm-editor'));
|
||||
const isTypingTarget = Boolean(
|
||||
target?.closest('input, textarea, [contenteditable="true"], [role="textbox"]')
|
||||
);
|
||||
if (isTypingTarget && !isEditorTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeElement = document.activeElement as Element | null;
|
||||
const editorHasFocus = Boolean(activeElement?.closest('.cm-editor'));
|
||||
if (!editorHasFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventMatchesShortcut(event, goToLineCombo)) {
|
||||
event.preventDefault();
|
||||
setIsGoToLineOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [canEdit, isMobile, shortcutOverrides, textViewMode]);
|
||||
setIsGoToLineOpen(true);
|
||||
});
|
||||
|
||||
const editorFontSize = useUIStore((state) => state.editorFontSize);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,102 +1,115 @@
|
||||
import React from 'react';
|
||||
import { focusChatInput } from '@/components/chat/composer/editor/dom';
|
||||
import { canUseElectronDesktopIPC, invokeDesktop } from '@/lib/desktop';
|
||||
import { eventMatchesShortcut, getEffectiveShortcutCombo } from '@/lib/shortcuts';
|
||||
import { ShortcutDispatcher } from '@/lib/shortcutDispatcher';
|
||||
import { shortcutRegistry } from '@/lib/shortcutRegistry';
|
||||
import { getEffectiveShortcutCombo } from '@/lib/shortcuts';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useSelectionStore } from '@/sync/selection-store';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useKeybinds } from './useKeybind';
|
||||
|
||||
export const useMiniChatKeyboardShortcuts = () => {
|
||||
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
|
||||
const currentDirectory = useDirectoryStore((state) => state.currentDirectory);
|
||||
const activeProject = useProjectsStore((state) => state.getActiveProject());
|
||||
const openNewSessionDraft = useSessionUIStore((state) => state.openNewSessionDraft);
|
||||
const dispatcherRef = React.useRef<ShortcutDispatcher | null>(null);
|
||||
|
||||
if (!dispatcherRef.current) {
|
||||
dispatcherRef.current = new ShortcutDispatcher({
|
||||
registry: shortcutRegistry,
|
||||
getBinding: (actionId) => getEffectiveShortcutCombo(
|
||||
actionId,
|
||||
useUIStore.getState().shortcutOverrides,
|
||||
),
|
||||
});
|
||||
}
|
||||
const dispatcher = dispatcherRef.current;
|
||||
|
||||
const cycleFavoriteModel = (delta: number): boolean | void => {
|
||||
const { favoriteModels, addRecentModel } = useUIStore.getState();
|
||||
if (favoriteModels.length === 0) return false;
|
||||
|
||||
const {
|
||||
currentProviderId,
|
||||
currentModelId,
|
||||
setProvider,
|
||||
setModel,
|
||||
} = useConfigStore.getState();
|
||||
const currentIndex = favoriteModels.findIndex(
|
||||
(favorite) => favorite.providerID === currentProviderId && favorite.modelID === currentModelId,
|
||||
);
|
||||
const next = favoriteModels[(currentIndex + delta + favoriteModels.length) % favoriteModels.length];
|
||||
setProvider(next.providerID);
|
||||
setModel(next.modelID);
|
||||
addRecentModel(next.providerID, next.modelID);
|
||||
};
|
||||
|
||||
useKeybinds({
|
||||
focus_input: () => {
|
||||
focusChatInput();
|
||||
},
|
||||
new_mini_chat: () => {
|
||||
if (!canUseElectronDesktopIPC()) return false;
|
||||
void invokeDesktop('desktop_open_draft_mini_chat_window', {
|
||||
directory: currentDirectory || activeProject?.path || '',
|
||||
projectId: activeProject?.id ?? null,
|
||||
})?.catch((error) => {
|
||||
console.warn('[mini-chat-shortcuts] failed to open draft mini chat window', error);
|
||||
});
|
||||
},
|
||||
new_chat: () => {
|
||||
openNewSessionDraft({
|
||||
selectedProjectId: activeProject?.id ?? null,
|
||||
directoryOverride: currentDirectory || activeProject?.path || null,
|
||||
preserveDirectoryOverride: Boolean(currentDirectory || activeProject?.path),
|
||||
});
|
||||
focusChatInput();
|
||||
},
|
||||
open_model_selector: () => {
|
||||
const { isModelSelectorOpen, setModelSelectorOpen } = useUIStore.getState();
|
||||
setModelSelectorOpen(!isModelSelectorOpen);
|
||||
},
|
||||
cycle_thinking_variant: () => {
|
||||
const configState = useConfigStore.getState();
|
||||
if (configState.getCurrentModelVariants().length === 0) return false;
|
||||
|
||||
configState.cycleCurrentVariant();
|
||||
const sessionId = useSessionUIStore.getState().currentSessionId;
|
||||
const {
|
||||
currentVariant,
|
||||
currentAgentName,
|
||||
currentProviderId,
|
||||
currentModelId,
|
||||
} = useConfigStore.getState();
|
||||
if (sessionId && currentAgentName && currentProviderId && currentModelId) {
|
||||
useSelectionStore.getState().saveAgentModelVariantForSession(
|
||||
sessionId,
|
||||
currentAgentName,
|
||||
currentProviderId,
|
||||
currentModelId,
|
||||
currentVariant,
|
||||
);
|
||||
}
|
||||
},
|
||||
cycle_favorite_model_forward: () => cycleFavoriteModel(1),
|
||||
cycle_favorite_model_backward: () => cycleFavoriteModel(-1),
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const combo = (actionId: string) => getEffectiveShortcutCombo(actionId, shortcutOverrides);
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (eventMatchesShortcut(event, combo('focus_input'))) {
|
||||
event.preventDefault();
|
||||
focusChatInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (canUseElectronDesktopIPC() && eventMatchesShortcut(event, combo('new_mini_chat'))) {
|
||||
event.preventDefault();
|
||||
void invokeDesktop('desktop_open_draft_mini_chat_window', {
|
||||
directory: currentDirectory || activeProject?.path || '',
|
||||
projectId: activeProject?.id ?? null,
|
||||
})?.catch((error) => {
|
||||
console.warn('[mini-chat-shortcuts] failed to open draft mini chat window', error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventMatchesShortcut(event, combo('new_chat'))) {
|
||||
event.preventDefault();
|
||||
openNewSessionDraft({
|
||||
selectedProjectId: activeProject?.id ?? null,
|
||||
directoryOverride: currentDirectory || activeProject?.path || null,
|
||||
preserveDirectoryOverride: Boolean(currentDirectory || activeProject?.path),
|
||||
});
|
||||
focusChatInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventMatchesShortcut(event, combo('open_model_selector'))) {
|
||||
event.preventDefault();
|
||||
const { isModelSelectorOpen, setModelSelectorOpen } = useUIStore.getState();
|
||||
setModelSelectorOpen(!isModelSelectorOpen);
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventMatchesShortcut(event, combo('cycle_thinking_variant'))) {
|
||||
const configState = useConfigStore.getState();
|
||||
const variants = configState.getCurrentModelVariants();
|
||||
if (variants.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
configState.cycleCurrentVariant();
|
||||
|
||||
const nextVariant = useConfigStore.getState().currentVariant;
|
||||
const sessionId = useSessionUIStore.getState().currentSessionId;
|
||||
const agentName = useConfigStore.getState().currentAgentName;
|
||||
const providerId = useConfigStore.getState().currentProviderId;
|
||||
const modelId = useConfigStore.getState().currentModelId;
|
||||
|
||||
if (sessionId && agentName && providerId && modelId) {
|
||||
useSelectionStore.getState().saveAgentModelVariantForSession(sessionId, agentName, providerId, modelId, nextVariant);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const cyclesForward = eventMatchesShortcut(event, combo('cycle_favorite_model_forward'));
|
||||
const cyclesBackward = eventMatchesShortcut(event, combo('cycle_favorite_model_backward'));
|
||||
if (cyclesForward || cyclesBackward) {
|
||||
const { favoriteModels, addRecentModel } = useUIStore.getState();
|
||||
if (favoriteModels.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const { currentProviderId, currentModelId, setProvider, setModel } = useConfigStore.getState();
|
||||
const currentIndex = favoriteModels.findIndex((favorite) => favorite.providerID === currentProviderId && favorite.modelID === currentModelId);
|
||||
const delta = cyclesForward ? 1 : -1;
|
||||
const next = favoriteModels[(currentIndex + delta + favoriteModels.length) % favoriteModels.length];
|
||||
|
||||
setProvider(next.providerID);
|
||||
setModel(next.modelID);
|
||||
addRecentModel(next.providerID, next.modelID);
|
||||
}
|
||||
if (dispatcher.dispatch(event)) event.preventDefault();
|
||||
};
|
||||
const handleBlur = () => dispatcher.handleBlur();
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [activeProject?.id, activeProject?.path, currentDirectory, openNewSessionDraft, shortcutOverrides]);
|
||||
window.addEventListener('blur', handleBlur);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('blur', handleBlur);
|
||||
};
|
||||
}, [dispatcher]);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user