Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 114 additions & 84 deletions src/main/presenter/shortcutPresenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,107 +35,133 @@ export class ShortcutPresenter {
}

// Command+N 或 Ctrl+N 创建新会话
globalShortcut.register(this.shortcutKeys.NewConversation, async () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
presenter.windowPresenter.sendToActiveTab(
focusedWindow.id,
SHORTCUT_EVENTS.CREATE_NEW_CONVERSATION
)
}
})
if (this.shortcutKeys.NewConversation) {
globalShortcut.register(this.shortcutKeys.NewConversation, async () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
presenter.windowPresenter.sendToActiveTab(
focusedWindow.id,
SHORTCUT_EVENTS.CREATE_NEW_CONVERSATION
)
}
})
}

// Command+Shift+N 或 Ctrl+Shift+N 创建新窗口
globalShortcut.register(this.shortcutKeys.NewWindow, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
eventBus.sendToMain(SHORTCUT_EVENTS.CREATE_NEW_WINDOW)
}
})
if (this.shortcutKeys.NewWindow) {
globalShortcut.register(this.shortcutKeys.NewWindow, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
eventBus.sendToMain(SHORTCUT_EVENTS.CREATE_NEW_WINDOW)
}
})
}

// Command+T 或 Ctrl+T 在当前窗口创建新标签页
globalShortcut.register(this.shortcutKeys.NewTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
eventBus.sendToMain(SHORTCUT_EVENTS.CREATE_NEW_TAB, focusedWindow.id)
}
})
if (this.shortcutKeys.NewTab) {
globalShortcut.register(this.shortcutKeys.NewTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
eventBus.sendToMain(SHORTCUT_EVENTS.CREATE_NEW_TAB, focusedWindow.id)
}
})
}

// Command+W 或 Ctrl+W 关闭当前标签页
globalShortcut.register(this.shortcutKeys.CloseTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
eventBus.sendToMain(SHORTCUT_EVENTS.CLOSE_CURRENT_TAB, focusedWindow.id)
}
})
if (this.shortcutKeys.CloseTab) {
globalShortcut.register(this.shortcutKeys.CloseTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
eventBus.sendToMain(SHORTCUT_EVENTS.CLOSE_CURRENT_TAB, focusedWindow.id)
}
})
}

// Command+Q 或 Ctrl+Q 退出程序
globalShortcut.register(this.shortcutKeys.Quit, () => {
app.quit()
})
if (this.shortcutKeys.Quit) {
globalShortcut.register(this.shortcutKeys.Quit, () => {
app.quit()
})
}

// Command+= 或 Ctrl+= 放大字体
globalShortcut.register(this.shortcutKeys.ZoomIn, () => {
eventBus.send(SHORTCUT_EVENTS.ZOOM_IN, SendTarget.ALL_WINDOWS)
})
if (this.shortcutKeys.ZoomIn) {
globalShortcut.register(this.shortcutKeys.ZoomIn, () => {
eventBus.send(SHORTCUT_EVENTS.ZOOM_IN, SendTarget.ALL_WINDOWS)
})
}

// Command+- 或 Ctrl+- 缩小字体
globalShortcut.register(this.shortcutKeys.ZoomOut, () => {
eventBus.send(SHORTCUT_EVENTS.ZOOM_OUT, SendTarget.ALL_WINDOWS)
})
if (this.shortcutKeys.ZoomOut) {
globalShortcut.register(this.shortcutKeys.ZoomOut, () => {
eventBus.send(SHORTCUT_EVENTS.ZOOM_OUT, SendTarget.ALL_WINDOWS)
})
}

// Command+0 或 Ctrl+0 重置字体大小
globalShortcut.register(this.shortcutKeys.ZoomResume, () => {
eventBus.send(SHORTCUT_EVENTS.ZOOM_RESUME, SendTarget.ALL_WINDOWS)
})
if (this.shortcutKeys.ZoomResume) {
globalShortcut.register(this.shortcutKeys.ZoomResume, () => {
eventBus.send(SHORTCUT_EVENTS.ZOOM_RESUME, SendTarget.ALL_WINDOWS)
})
}

// Command+, 或 Ctrl+, 打开设置
globalShortcut.register(this.shortcutKeys.GoSettings, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
presenter.windowPresenter.sendToActiveTab(focusedWindow.id, SHORTCUT_EVENTS.GO_SETTINGS)
}
})
if (this.shortcutKeys.GoSettings) {
globalShortcut.register(this.shortcutKeys.GoSettings, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
presenter.windowPresenter.sendToActiveTab(focusedWindow.id, SHORTCUT_EVENTS.GO_SETTINGS)
}
})
}

// Command+L 或 Ctrl+L 清除聊天历史
globalShortcut.register(this.shortcutKeys.CleanChatHistory, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
presenter.windowPresenter.sendToActiveTab(
focusedWindow.id,
SHORTCUT_EVENTS.CLEAN_CHAT_HISTORY
)
}
})
if (this.shortcutKeys.CleanChatHistory) {
globalShortcut.register(this.shortcutKeys.CleanChatHistory, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
presenter.windowPresenter.sendToActiveTab(
focusedWindow.id,
SHORTCUT_EVENTS.CLEAN_CHAT_HISTORY
)
}
})
}

// Command+D 或 Ctrl+D 清除聊天历史
globalShortcut.register(this.shortcutKeys.DeleteConversation, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
presenter.windowPresenter.sendToActiveTab(
focusedWindow.id,
SHORTCUT_EVENTS.DELETE_CONVERSATION
)
}
})
if (this.shortcutKeys.DeleteConversation) {
globalShortcut.register(this.shortcutKeys.DeleteConversation, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
presenter.windowPresenter.sendToActiveTab(
focusedWindow.id,
SHORTCUT_EVENTS.DELETE_CONVERSATION
)
}
})
}

// 添加标签页切换相关快捷键

// Command+Tab 或 Ctrl+Tab 切换到下一个标签页
globalShortcut.register(this.shortcutKeys.SwitchNextTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
this.switchToNextTab(focusedWindow.id)
}
})
if (this.shortcutKeys.SwitchNextTab) {
globalShortcut.register(this.shortcutKeys.SwitchNextTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
this.switchToNextTab(focusedWindow.id)
}
})
}

// Ctrl+Shift+Tab 切换到上一个标签页
globalShortcut.register(this.shortcutKeys.SwitchPrevTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
this.switchToPreviousTab(focusedWindow.id)
}
})
if (this.shortcutKeys.SwitchPrevTab) {
globalShortcut.register(this.shortcutKeys.SwitchPrevTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
this.switchToPreviousTab(focusedWindow.id)
}
})
}

// 注册标签页数字快捷键 (1-8)
for (let i = 1; i <= 8; i++) {
Expand All @@ -148,12 +174,14 @@ export class ShortcutPresenter {
}

// Command+9 或 Ctrl+9 切换到最后一个标签页
globalShortcut.register(this.shortcutKeys.SwtichToLastTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
this.switchToLastTab(focusedWindow.id)
}
})
if (this.shortcutKeys.SwtichToLastTab) {
globalShortcut.register(this.shortcutKeys.SwtichToLastTab, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
this.switchToLastTab(focusedWindow.id)
}
})
}

this.showHideWindow()

Expand Down Expand Up @@ -229,9 +257,11 @@ export class ShortcutPresenter {
// Command+O 或 Ctrl+O 显示/隐藏窗口
private async showHideWindow() {
// Command+O 或 Ctrl+O 显示/隐藏窗口
globalShortcut.register(this.shortcutKeys.ShowHideWindow, () => {
eventBus.sendToMain(TRAY_EVENTS.SHOW_HIDDEN_WINDOW)
})
if (this.shortcutKeys.ShowHideWindow) {
globalShortcut.register(this.shortcutKeys.ShowHideWindow, () => {
eventBus.sendToMain(TRAY_EVENTS.SHOW_HIDDEN_WINDOW)
})
}
}

unregisterShortcuts(): void {
Expand Down
83 changes: 44 additions & 39 deletions src/renderer/src/components/settings/ShortcutSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,20 @@
<div class="w-full h-full flex flex-col gap-1.5">
<!-- 快捷键列表 -->
<div class="flex flex-col gap-2">
<div
v-for="shortcut in shortcuts"
:key="shortcut.id"
class="flex flex-row p-2 items-center gap-2 px-2"
>
<span
class="flex flex-row items-center gap-2 flex-grow w-full"
:dir="languageStore.dir"
>
<div v-for="shortcut in shortcuts" :key="shortcut.id" class="flex flex-row p-2 items-center gap-2 px-2">
<span class="flex flex-row items-center gap-2 flex-grow w-full" :dir="languageStore.dir">
<Icon :icon="shortcut.icon" class="w-4 h-4 text-muted-foreground" />
<span class="text-sm font-medium">{{ t(shortcut.label) }}</span>
</span>

<div class="flex-shrink-0 min-w-32">
<div class="relative w-full">
<Button
variant="outline"
class="h-10 min-w-[200px] justify-end relative px-2"
:class="{
'ring-2 ring-primary': recordingShortcutId === shortcut.id && !shortcutError,
'ring-2 ring-destructive': recordingShortcutId === shortcut.id && shortcutError
}"
:disabled="shortcut.disabled"
@click="startRecording(shortcut.id)"
>
<span
v-if="recordingShortcutId === shortcut.id"
class="text-sm"
:class="{ 'text-primary': !shortcutError, 'text-destructive': shortcutError }"
>
<div class="relative w-full group">
<Button variant="outline" class="h-10 min-w-[200px] justify-end relative px-2" :class="{
'ring-2 ring-primary': recordingShortcutId === shortcut.id && !shortcutError,
'ring-2 ring-destructive': recordingShortcutId === shortcut.id && shortcutError
}" :disabled="shortcut.disabled" @click="startRecording(shortcut.id)">
<span v-if="recordingShortcutId === shortcut.id" class="text-sm"
:class="{ 'text-primary': !shortcutError, 'text-destructive': shortcutError }">
<span v-if="shortcutError">
{{ shortcutError }}
</span>
Expand All @@ -58,22 +43,24 @@
<span v-else>{{ t('settings.shortcuts.pressKeys') }}</span>
</span>
<span v-else class="text-sm">
<span
v-for="(key, idx) in shortcut.key"
:key="idx"
class="tw-keycap"
:class="{
'font-mono tracking-widest': key === '0'
}"
>
<span v-for="(key, idx) in shortcut.key" :key="idx" class="tw-keycap" :class="{
'font-mono tracking-widest': key === '0'
}">
{{ key }}
</span>
</span>
<Icon
v-if="recordingShortcutId !== shortcut.id"
icon="lucide:pencil"
class="w-3.5 h-3.5 text-muted-foreground"
/>
<Icon v-if="recordingShortcutId !== shortcut.id" icon="lucide:pencil"
class="w-3.5 h-3.5 text-muted-foreground group-hover:opacity-0 transition-opacity" />
</Button>

<!-- 清理图标 - 只在hover时显示且不是禁用状态 -->
<Button v-if="!shortcut.disabled && recordingShortcutId !== shortcut.id"
variant="ghost"
size="sm"
class="absolute right-1 top-1/2 -translate-y-1/2 h-8 w-8 p-0 opacity-0 group-hover:opacity-100 transition-opacity z-10 hover:bg-destructive/10"
@click.stop="clearShortcut(shortcut.id)"
:title="t('settings.shortcuts.clearShortcut')">
<Icon icon="lucide:x" class="w-4 h-4 text-destructive" />
</Button>
</div>
</div>
Expand Down Expand Up @@ -278,6 +265,7 @@ const resetShortcutKeys = async () => {
try {
await shortcutKeyStore.resetShortcutKeys()
// 确保快捷键功能重新启用
shortcutKeyStore.disableShortcutKey()
shortcutKeyStore.enableShortcutKey()
} catch (error) {
console.error('重置快捷键失败:', error)
Expand Down Expand Up @@ -404,13 +392,30 @@ const saveChanges = async () => {
try {
await shortcutKeyStore.saveShortcutKeys()

// 重新注册快捷键
shortcutKeyStore.disableShortcutKey()
shortcutKeyStore.enableShortcutKey()
} catch (error) {
console.error('Save shortcut keys error:', error)
}
}

// 清理快捷键
const clearShortcut = async (shortcutId: string) => {
if (!shortcutKeys.value) return

try {
// 设置为空字符串
const shortcutKey = shortcutId as keyof typeof shortcutKeys.value
shortcutKeys.value[shortcutKey] = ''

// 保存更改
await saveChanges()

console.log(`Shortcut ${shortcutId} cleared`)
} catch (error) {
console.error('Clear shortcut error:', error)
}
}
</script>

<style scoped>
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/i18n/en-US/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
"pressEnterToSave": "Press Enter to save, Esc to cancel",
"noModifierOnly": "Cannot use modifier key alone as shortcut",
"keyConflict": "Shortcut key conflict, please choose another combination",
"clearShortcut": "Clear shortcut",
"cleanHistory": "Clear chat history",
"deleteConversation": "Delete Conversation",
"goSettings": "Open Settings",
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/i18n/fa-IR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
"pressEnterToSave": "برای نگهداری Enter را فشار دهید، برای رد کردن Esc را بزنید",
"noModifierOnly": "نمی‌توان از کلید اصلاح‌کننده به‌تنهایی به‌عنوان میانبر استفاده کرد",
"keyConflict": "ناسازگاری کلید میانبر، لطفاً ترکیب دیگری انتخاب کنید",
"clearShortcut": "پاک کردن میانبر",
"cleanHistory": "تمیز کردن تاریخچه گفت‌وگو",
"deleteConversation": "پاک کردن گفت‌وگو",
"goSettings": "باز کردن تنظیمات",
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/i18n/fr-FR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
"pressEnterToSave": "Appuyez sur Entrée pour enregistrer, Échap pour annuler",
"noModifierOnly": "Impossible d'utiliser uniquement une touche de modification comme raccourci",
"keyConflict": "Conflit de raccourci clavier, veuillez choisir une autre combinaison",
"clearShortcut": "Effacer le raccourci",
"cleanHistory": "Historique de chat clair",
"deleteConversation": "Supprimer la conversation",
"goSettings": "Paramètres ouvrir",
Expand Down
Loading