From bb1c39dd52d1195570e9325e6224c87037a04370 Mon Sep 17 00:00:00 2001
From: icinggslits <93433084+icinggslits@users.noreply.github.com>
Date: Tue, 25 Feb 2025 19:54:31 +0800
Subject: [PATCH 1/2] feature: Add switch navigation shortcut (#2119)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feature: Add switch navigation shortcut
# Conflicts:
# src/renderer/src/i18n/locales/en-us.json
# src/renderer/src/i18n/locales/ja-jp.json
# src/renderer/src/i18n/locales/ru-ru.json
# src/renderer/src/i18n/locales/zh-tw.json
# src/renderer/src/pages/translate/TranslatePage.tsx
# Conflicts:
# src/renderer/src/store/migrate.ts
* 复原
* 尝试提交
* 尝试回滚
* feat: Add switch navigation shortcut
---
src/renderer/src/components/app/Sidebar.tsx | 20 +++--
.../src/handler/NavigationHandler.tsx | 89 ++++++++++++++++++-
src/renderer/src/i18n/locales/en-us.json | 8 +-
src/renderer/src/i18n/locales/ja-jp.json | 8 +-
src/renderer/src/i18n/locales/ru-ru.json | 8 +-
src/renderer/src/i18n/locales/zh-cn.json | 8 +-
src/renderer/src/i18n/locales/zh-tw.json | 8 +-
src/renderer/src/pages/agents/AgentsPage.tsx | 48 +++++++++-
src/renderer/src/pages/apps/AppsPage.tsx | 1 +
src/renderer/src/pages/files/FilesPage.tsx | 32 ++++++-
.../src/pages/home/Tabs/AssistantsTab.tsx | 26 +++++-
.../src/pages/home/Tabs/TopicsTab.tsx | 15 ++++
src/renderer/src/pages/home/Tabs/index.tsx | 49 ++++++----
.../src/pages/knowledge/KnowledgePage.tsx | 19 ++++
.../src/pages/settings/SettingsPage.tsx | 26 +++++-
.../src/pages/translate/TranslatePage.tsx | 43 +++------
src/renderer/src/store/migrate.ts | 47 ++++++++++
src/renderer/src/store/shortcuts.ts | 42 +++++++++
18 files changed, 430 insertions(+), 67 deletions(-)
diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx
index 053913c202..8c288ca4e0 100644
--- a/src/renderer/src/components/app/Sidebar.tsx
+++ b/src/renderer/src/components/app/Sidebar.tsx
@@ -26,6 +26,16 @@ import MinAppIcon from '../Icons/MinAppIcon'
import MinApp from '../MinApp'
import UserPopup from '../Popups/UserPopup'
+export const locationPathnameMappingPathMap = {
+ assistants: '/',
+ agents: '/agents',
+ paintings: '/paintings',
+ translate: '/translate',
+ minapp: '/apps',
+ knowledge: '/knowledge',
+ files: '/files'
+}
+
const Sidebar: FC = () => {
const { pathname } = useLocation()
const avatar = useAvatar()
@@ -133,15 +143,7 @@ const MainMenus: FC = () => {
files:
}
- const pathMap = {
- assistants: '/',
- agents: '/agents',
- paintings: '/paintings',
- translate: '/translate',
- minapp: '/apps',
- knowledge: '/knowledge',
- files: '/files'
- }
+ const pathMap = locationPathnameMappingPathMap
return sidebarIcons.visible.map((icon) => {
const path = pathMap[icon]
diff --git a/src/renderer/src/handler/NavigationHandler.tsx b/src/renderer/src/handler/NavigationHandler.tsx
index c780e467c3..f5acbf4191 100644
--- a/src/renderer/src/handler/NavigationHandler.tsx
+++ b/src/renderer/src/handler/NavigationHandler.tsx
@@ -1,8 +1,16 @@
+import { locationPathnameMappingPathMap } from '@renderer/components/app/Sidebar'
+import { useSettings } from '@renderer/hooks/useSettings'
+import { useShortcut } from '@renderer/hooks/useShortcuts'
+import { settingMenuItemPathList } from '@renderer/pages/settings/SettingsPage'
+import Logger from 'electron-log'
+import React from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
-import { useNavigate } from 'react-router-dom'
+import { useLocation, useNavigate } from 'react-router-dom'
const NavigationHandler: React.FC = () => {
const navigate = useNavigate()
+ const { sidebarIcons } = useSettings()
+ const { pathname: navigationPathname } = useLocation()
useHotkeys(
'meta+, ! ctrl+,',
function () {
@@ -11,6 +19,85 @@ const NavigationHandler: React.FC = () => {
{ splitKey: '!' }
)
+ const navigationListAndIndex = (): {
+ err: boolean
+ errorMessage: string
+ navigationList: string[]
+ index: number
+ defaultSettingSubPathname?: string
+ } => {
+ const pinnedPathname = sidebarIcons.visible.map((iconName) => locationPathnameMappingPathMap[iconName])
+ if (pinnedPathname.length !== sidebarIcons.visible.length) {
+ return {
+ err: true,
+ errorMessage: '[NavigationHandler] pinnedPathname length not match',
+ navigationList: [],
+ index: 0
+ }
+ } else {
+ const navigationList = [...pinnedPathname, '/settings']
+ const currentPathname = (() => {
+ const path = navigationPathname.split('/')[1]
+ if (path === '') {
+ return '/'
+ } else {
+ return `/${path}`
+ }
+ })()
+
+ const index = navigationList.findIndex((iconName) => iconName === currentPathname)
+ if (index === -1) {
+ return {
+ err: true,
+ errorMessage: '[NavigationHandler] currentPathname not found',
+ navigationList,
+ index: 0
+ }
+ } else {
+ return {
+ err: false,
+ errorMessage: '',
+ navigationList,
+ index
+ }
+ }
+ }
+ }
+
+ const gotoNavigateByIndex = (navigationList: string[], index: number) => {
+ const listLength = navigationList.length
+ let i = index % listLength
+ if (i < 0) {
+ i = (i + listLength) % listLength
+ }
+ const targetPathname = navigationList[i]
+ if (targetPathname.startsWith('/settings')) {
+ navigate(settingMenuItemPathList?.[0] ?? '/settings/provider')
+ } else {
+ navigate(targetPathname)
+ }
+ }
+
+ useShortcut('switch_to_prev_main_navigation', () => {
+ const result = navigationListAndIndex()
+ if (result.err) {
+ Logger.error(result.errorMessage)
+ } else {
+ const { navigationList, index } = result
+ gotoNavigateByIndex(navigationList, index - 1)
+ }
+ })
+
+ useShortcut('switch_to_next_main_navigation', () => {
+ const result = navigationListAndIndex()
+ if (result.err) {
+ Logger.error(result.errorMessage)
+ } else {
+ const { navigationList, index } = result
+ gotoNavigateByIndex(navigationList, index + 1)
+ }
+ })
+
return null
}
diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json
index e9985955f6..3058509c6d 100644
--- a/src/renderer/src/i18n/locales/en-us.json
+++ b/src/renderer/src/i18n/locales/en-us.json
@@ -805,7 +805,13 @@
"toggle_show_topics": "Toggle Topics",
"zoom_in": "Zoom In",
"zoom_out": "Zoom Out",
- "zoom_reset": "Reset Zoom"
+ "zoom_reset": "Reset Zoom",
+ "switch_to_prev_main_navigation": "Switch to previous main navigation",
+ "switch_to_next_main_navigation": "Switch to next main navigation",
+ "switch_to_prev_main_tab": "Switch to previous main tab",
+ "switch_to_next_main_tab": "Switch to next main tab",
+ "switch_to_prev_horizontal_tab": "Switch to previous horizontal tab",
+ "switch_to_next_horizontal_tab": "Switch to next horizontal tab"
},
"theme.auto": "Auto",
"theme.dark": "Dark",
diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json
index 6419b8045d..d93ab55d34 100644
--- a/src/renderer/src/i18n/locales/ja-jp.json
+++ b/src/renderer/src/i18n/locales/ja-jp.json
@@ -804,7 +804,13 @@
"toggle_show_topics": "トピックの表示を切り替え",
"zoom_in": "ズームイン",
"zoom_out": "ズームアウト",
- "zoom_reset": "ズームをリセット"
+ "zoom_reset": "ズームをリセット",
+ "switch_to_prev_main_navigation": "前のメインナビゲーションに切り替え",
+ "switch_to_next_main_navigation": "次のメインナビゲーションに切り替え",
+ "switch_to_prev_main_tab": "前のメインタブに切り替え",
+ "switch_to_next_main_tab": "次のメインタブに切り替え",
+ "switch_to_prev_horizontal_tab": "前の水平タブに切り替え",
+ "switch_to_next_horizontal_tab": "次の水平タブに切り替え"
},
"theme.auto": "自動",
"theme.dark": "ダークテーマ",
diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json
index 0389ab8114..cc25e10890 100644
--- a/src/renderer/src/i18n/locales/ru-ru.json
+++ b/src/renderer/src/i18n/locales/ru-ru.json
@@ -804,7 +804,13 @@
"toggle_show_topics": "Переключить отображение топиков",
"zoom_in": "Увеличить",
"zoom_out": "Уменьшить",
- "zoom_reset": "Сбросить масштаб"
+ "zoom_reset": "Сбросить масштаб",
+ "switch_to_prev_main_navigation": "Переключиться к предыдущей главной навигации",
+ "switch_to_next_main_navigation": "Переключиться к следующей главной навигации",
+ "switch_to_prev_main_tab": "Переключиться к предыдущей главной вкладке",
+ "switch_to_next_main_tab": "Переключиться к следующей главной вкладке",
+ "switch_to_prev_horizontal_tab": "Переключиться к предыдущей горизонтальной вкладке",
+ "switch_to_next_horizontal_tab": "Переключиться к следующей горизонтальной вкладке"
},
"theme.auto": "Автоматически",
"theme.dark": "Темная",
diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json
index 7afb89d9a4..b7b5bbbe87 100644
--- a/src/renderer/src/i18n/locales/zh-cn.json
+++ b/src/renderer/src/i18n/locales/zh-cn.json
@@ -804,7 +804,13 @@
"toggle_show_topics": "切换话题显示",
"zoom_in": "放大界面",
"zoom_out": "缩小界面",
- "zoom_reset": "重置缩放"
+ "zoom_reset": "重置缩放",
+ "switch_to_prev_main_navigation": "导航栏向上切换",
+ "switch_to_next_main_navigation": "导航栏向下切换",
+ "switch_to_prev_main_tab": "侧边栏向上切换",
+ "switch_to_next_main_tab": "侧边栏向下切换",
+ "switch_to_prev_horizontal_tab": "选项卡向左切换",
+ "switch_to_next_horizontal_tab": "选项卡向右切换"
},
"theme.auto": "跟随系统",
"theme.dark": "深色主题",
diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json
index 50b733c7f2..8fc1991d50 100644
--- a/src/renderer/src/i18n/locales/zh-tw.json
+++ b/src/renderer/src/i18n/locales/zh-tw.json
@@ -804,7 +804,13 @@
"toggle_show_topics": "切換話題顯示",
"zoom_in": "放大界面",
"zoom_out": "縮小界面",
- "zoom_reset": "重置縮放"
+ "zoom_reset": "重置縮放",
+ "switch_to_prev_main_navigation": "導航欄向上切換",
+ "switch_to_next_main_navigation": "導航欄向下切換",
+ "switch_to_prev_main_tab": "側邊欄向上切換",
+ "switch_to_next_main_tab": "側邊欄向下切換",
+ "switch_to_prev_horizontal_tab": "選項卡向左切換",
+ "switch_to_next_horizontal_tab": "選項卡向右切換"
},
"theme.auto": "自動",
"theme.dark": "深色主題",
diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx
index a8a30bba8f..922305956a 100644
--- a/src/renderer/src/pages/agents/AgentsPage.tsx
+++ b/src/renderer/src/pages/agents/AgentsPage.tsx
@@ -1,6 +1,7 @@
import { SearchOutlined } from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import Scrollbar from '@renderer/components/Scrollbar'
+import { useShortcut } from '@renderer/hooks/useShortcuts'
import { createAssistantFromAgent } from '@renderer/services/AssistantService'
import { Agent } from '@renderer/types'
import { uuid } from '@renderer/utils'
@@ -24,6 +25,7 @@ const AgentsPage: FC = () => {
const [search, setSearch] = useState('')
const [searchInput, setSearchInput] = useState('')
const systemAgents = useSystemAgents()
+ const [tabActiveKey, setTabActiveKey] = useState('1')
const agentGroups = useMemo(() => {
if (Object.keys(_agentGroups).length === 0) {
@@ -159,6 +161,24 @@ const AgentsPage: FC = () => {
}
}
+ useShortcut('switch_to_prev_main_tab', () => {
+ if (tabItems.length > 1) {
+ const index = tabItems.findIndex((item) => item.key === tabActiveKey)
+ if (index !== -1) {
+ setTabActiveKey(tabItems[index === 0 ? tabItems.length - 1 : index - 1].key)
+ }
+ }
+ })
+
+ useShortcut('switch_to_next_main_tab', () => {
+ if (tabItems.length > 1) {
+ const index = tabItems.findIndex((item) => item.key === tabActiveKey)
+ if (index !== -1) {
+ setTabActiveKey(tabItems[index === tabItems.length - 1 ? 0 : index + 1].key)
+ }
+ }
+ })
+
return (
@@ -177,6 +197,7 @@ const AgentsPage: FC = () => {
maxLength={50}
onChange={(e) => setSearchInput(e.target.value)}
onPressEnter={handleSearch}
+ autoFocus
/>
@@ -187,7 +208,14 @@ const AgentsPage: FC = () => {
search.trim() ? (
{renderAgentList(Object.values(filteredAgentGroups).flat())}
) : (
-
+ setTabActiveKey(key)}
+ />
)
) : (
@@ -251,7 +279,11 @@ const EmptyView = styled.div`
color: var(--color-text-secondary);
`
-const Tabs = styled(TabsAntd)<{ $language: string }>`
+const Tabs = styled(TabsAntd)<{
+ $language: string
+ activeKey: string
+ onTabClick: (key: string, event: MouseEvent) => void
+}>`
display: flex;
flex: 1;
flex-direction: row-reverse;
@@ -259,18 +291,22 @@ const Tabs = styled(TabsAntd)<{ $language: string }>`
.ant-tabs-tabpane {
padding-right: 0 !important;
}
+
.ant-tabs-nav {
min-width: ${({ $language }) => ($language.startsWith('zh') ? '120px' : '140px')};
max-width: ${({ $language }) => ($language.startsWith('zh') ? '120px' : '140px')};
position: relative;
overflow: hidden;
}
+
.ant-tabs-nav-list {
padding: 10px 8px;
}
+
.ant-tabs-nav-operations {
display: none !important;
}
+
.ant-tabs-tab {
margin: 0 !important;
border-radius: var(--list-item-border-radius);
@@ -283,6 +319,7 @@ const Tabs = styled(TabsAntd)<{ $language: string }>`
user-select: none;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
outline: none !important;
+
.ant-tabs-tab-btn {
white-space: nowrap;
overflow: hidden;
@@ -291,31 +328,38 @@ const Tabs = styled(TabsAntd)<{ $language: string }>`
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
outline: none !important;
}
+
&:hover {
color: var(--color-text) !important;
background-color: var(--color-background-soft);
}
}
+
.ant-tabs-tab-active {
background-color: var(--color-background-soft);
border: 0.5px solid var(--color-border);
transform: scale(1.02);
}
+
.ant-tabs-content-holder {
border-left: 0.5px solid var(--color-border);
border-right: none;
}
+
.ant-tabs-ink-bar {
display: none;
}
+
.ant-tabs-tab-btn:active {
color: var(--color-text) !important;
}
+
.ant-tabs-tab-active {
.ant-tabs-tab-btn {
color: var(--color-text) !important;
}
}
+
.ant-tabs-content {
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx
index 912ca371b0..ece3f17750 100644
--- a/src/renderer/src/pages/apps/AppsPage.tsx
+++ b/src/renderer/src/pages/apps/AppsPage.tsx
@@ -46,6 +46,7 @@ const AppsPage: FC = () => {
suffix={}
value={search}
onChange={(e) => setSearch(e.target.value)}
+ autoFocus
/>
diff --git a/src/renderer/src/pages/files/FilesPage.tsx b/src/renderer/src/pages/files/FilesPage.tsx
index a248ef6486..68240f0a13 100644
--- a/src/renderer/src/pages/files/FilesPage.tsx
+++ b/src/renderer/src/pages/files/FilesPage.tsx
@@ -11,6 +11,7 @@ import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
import Scrollbar from '@renderer/components/Scrollbar'
import db from '@renderer/databases'
import { useProviders } from '@renderer/hooks/useProvider'
+import { useShortcut } from '@renderer/hooks/useShortcuts'
import FileManager from '@renderer/services/FileManager'
import store from '@renderer/store'
import { FileType, FileTypes } from '@renderer/types'
@@ -19,12 +20,19 @@ import type { MenuProps } from 'antd'
import { Button, Dropdown, Menu } from 'antd'
import dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks'
-import { FC, useMemo, useState } from 'react'
+import React, { FC, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import ContentView from './ContentView'
+interface MenuItem {
+ key: string
+ label: string
+ icon: React.ReactNode
+ onClick?: () => void
+}
+
const FilesPage: FC = () => {
const { t } = useTranslation()
const [fileType, setFileType] = useState('all')
@@ -42,7 +50,7 @@ const FilesPage: FC = () => {
const handleDelete = async (fileId: string) => {
const file = await FileManager.getFile(fileId)
- const paintings = await store.getState().paintings.paintings
+ const paintings = store.getState().paintings.paintings
const paintingsFiles = paintings.flatMap((p) => p.files)
if (paintingsFiles.some((p) => p.id === fileId)) {
@@ -180,6 +188,26 @@ const FilesPage: FC = () => {
}))
].filter(Boolean) as MenuProps['items']
+ useShortcut('switch_to_prev_main_tab', () => {
+ const items = menuItems as MenuItem[]
+ if (items.length > 1) {
+ const index = items.findIndex((item) => item.key === fileType)
+ if (index !== -1) {
+ setFileType(items[index === 0 ? items.length - 1 : index - 1].key as FileTypes)
+ }
+ }
+ })
+
+ useShortcut('switch_to_next_main_tab', () => {
+ const items = menuItems as MenuItem[]
+ if (items.length > 1) {
+ const index = items.findIndex((item) => item.key === fileType)
+ if (index !== -1) {
+ setFileType(items[index === items.length - 1 ? 0 : index + 1].key as FileTypes)
+ }
+ }
+ })
+
return (
diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx
index 93f9480421..be0fedc709 100644
--- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx
+++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx
@@ -2,7 +2,13 @@ import { PlusOutlined } from '@ant-design/icons'
import DragableList from '@renderer/components/DragableList'
import Scrollbar from '@renderer/components/Scrollbar'
import { useAgents } from '@renderer/hooks/useAgents'
-import { useAssistants } from '@renderer/hooks/useAssistant'
+import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
+import { modelGenerating } from '@renderer/hooks/useRuntime'
+import { useSettings } from '@renderer/hooks/useSettings'
+import { useShortcut } from '@renderer/hooks/useShortcuts'
+import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
+import { getDefaultTopic } from '@renderer/services/AssistantService'
+import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { Assistant } from '@renderer/types'
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -38,6 +44,24 @@ const Assistants: FC = ({
[assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant]
)
+ useShortcut('switch_to_prev_main_tab', () => {
+ if (assistants.length > 1) {
+ const assistantIndex = assistants.findIndex((assistant) => assistant.id === activeAssistant?.id)
+ if (assistantIndex !== -1) {
+ onSwitchAssistant(assistants[assistantIndex === 0 ? assistants.length - 1 : assistantIndex - 1]).then()
+ }
+ }
+ })
+
+ useShortcut('switch_to_next_main_tab', () => {
+ if (assistants.length > 1) {
+ const assistantIndex = assistants.findIndex((assistant) => assistant.id === activeAssistant?.id)
+ if (assistantIndex !== -1) {
+ onSwitchAssistant(assistants[assistantIndex === assistants.length - 1 ? 0 : assistantIndex + 1]).then()
+ }
+ }
+ })
+
return (
= ({ assistant: _assistant, activeTopic, setActiveTopic
[assistant, assistants, onClearMessages, onDeleteTopic, onPinTopic, onMoveTopic, t, updateTopic]
)
+ useShortcut('switch_to_prev_main_tab', () => {
+ const index = assistant.topics.findIndex((topic) => topic.id === activeTopic.id)
+ if (index !== -1) {
+ onSwitchTopic(assistant.topics[index === 0 ? assistant.topics.length - 1 : index - 1]).then()
+ }
+ })
+
+ useShortcut('switch_to_next_main_tab', () => {
+ const index = assistant.topics.findIndex((topic) => topic.id === activeTopic.id)
+ if (index !== -1) {
+ onSwitchTopic(assistant.topics[index === assistant.topics.length - 1 ? 0 : index + 1]).then()
+ }
+ })
+
return (
diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx
index a802475079..15161dc3b1 100644
--- a/src/renderer/src/pages/home/Tabs/index.tsx
+++ b/src/renderer/src/pages/home/Tabs/index.tsx
@@ -2,6 +2,7 @@ import { BarsOutlined, SettingOutlined } from '@ant-design/icons'
import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup'
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
+import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { Assistant, Topic } from '@renderer/types'
@@ -52,6 +53,20 @@ const HomeTabs: FC = ({ activeAssistant, activeTopic, setActiveAssistant,
icon:
}
+ const segmentedOptions = [
+ position === 'left' && topicPosition === 'left' ? assistantTab : undefined,
+ {
+ label: t('common.topics'),
+ value: 'topic',
+ icon:
+ },
+ {
+ label: t('settings.title'),
+ value: 'settings',
+ icon:
+ }
+ ].filter(Boolean) as SegmentedProps['options']
+
const onCreateAssistant = async () => {
const assistant = await AddAssistantPopup.show()
assistant && setActiveAssistant(assistant)
@@ -93,6 +108,24 @@ const HomeTabs: FC = ({ activeAssistant, activeTopic, setActiveAssistant,
}
}, [position, tab, topicPosition])
+ useShortcut('switch_to_prev_horizontal_tab', () => {
+ if (segmentedOptions.length > 1) {
+ const index = segmentedOptions.findIndex((option) => option.value === tab)
+ if (index !== -1) {
+ setTab(segmentedOptions[index === 0 ? segmentedOptions.length - 1 : index - 1].value as 'topic' | 'settings')
+ }
+ }
+ })
+
+ useShortcut('switch_to_next_horizontal_tab', () => {
+ if (segmentedOptions.length > 1) {
+ const index = segmentedOptions.findIndex((option) => option.value === tab)
+ if (index !== -1) {
+ setTab(segmentedOptions[index === segmentedOptions.length - 1 ? 0 : index + 1].value as 'topic' | 'settings')
+ }
+ }
+ })
+
return (
{showTab && (
@@ -106,21 +139,7 @@ const HomeTabs: FC = ({ activeAssistant, activeTopic, setActiveAssistant,
borderBottom: '0.5px solid var(--color-border)',
gap: 2
}}
- options={
- [
- position === 'left' && topicPosition === 'left' ? assistantTab : undefined,
- {
- label: t('common.topics'),
- value: 'topic',
- icon:
- },
- {
- label: t('settings.title'),
- value: 'settings',
- icon:
- }
- ].filter(Boolean) as SegmentedProps['options']
- }
+ options={segmentedOptions}
onChange={(value) => setTab(value as 'topic' | 'settings')}
block
/>
diff --git a/src/renderer/src/pages/knowledge/KnowledgePage.tsx b/src/renderer/src/pages/knowledge/KnowledgePage.tsx
index 98b6acef2e..2cb54462ea 100644
--- a/src/renderer/src/pages/knowledge/KnowledgePage.tsx
+++ b/src/renderer/src/pages/knowledge/KnowledgePage.tsx
@@ -5,6 +5,7 @@ import ListItem from '@renderer/components/ListItem'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import Scrollbar from '@renderer/components/Scrollbar'
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
+import { useShortcut } from '@renderer/hooks/useShortcuts'
import { KnowledgeBase } from '@renderer/types'
import { Dropdown, Empty, MenuProps } from 'antd'
import { FC, useCallback, useEffect, useState } from 'react'
@@ -81,6 +82,24 @@ const KnowledgePage: FC = () => {
[deleteKnowledgeBase, renameKnowledgeBase, t]
)
+ useShortcut('switch_to_prev_main_tab', () => {
+ if (bases.length > 1) {
+ const index = bases.findIndex((item) => item.id === selectedBase?.id)
+ if (index !== -1) {
+ setSelectedBase(bases[index === 0 ? bases.length - 1 : index - 1])
+ }
+ }
+ })
+
+ useShortcut('switch_to_next_main_tab', () => {
+ if (bases.length > 1) {
+ const index = bases.findIndex((item) => item.id === selectedBase?.id)
+ if (index !== -1) {
+ setSelectedBase(bases[index === bases.length - 1 ? 0 : index + 1])
+ }
+ }
+ })
+
return (
diff --git a/src/renderer/src/pages/settings/SettingsPage.tsx b/src/renderer/src/pages/settings/SettingsPage.tsx
index f12a115af5..c4508dbf72 100644
--- a/src/renderer/src/pages/settings/SettingsPage.tsx
+++ b/src/renderer/src/pages/settings/SettingsPage.tsx
@@ -12,7 +12,7 @@ import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { isLocalAi } from '@renderer/config/env'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
-import { Link, Route, Routes, useLocation } from 'react-router-dom'
+import { Link, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import AboutSettings from './AboutSettings'
@@ -23,13 +23,35 @@ import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings'
import ProvidersList from './ProviderSettings'
import QuickAssistantSettings from './QuickAssistantSettings'
import ShortcutSettings from './ShortcutSettings'
+import { useShortcut } from '@renderer/hooks/useShortcuts'
import WebSearchSettings from './WebSearchSettings'
+export let settingMenuItemPathList: string[] = []
+
const SettingsPage: FC = () => {
const { pathname } = useLocation()
const { t } = useTranslation()
+ const navigate = useNavigate()
+ settingMenuItemPathList = []
+
+ const isRoute = (path: string) => {
+ settingMenuItemPathList.push(path)
+ return ((path: string): string => (pathname.startsWith(path) ? 'active' : ''))(path)
+ }
+
+ useShortcut('switch_to_prev_main_tab', () => {
+ const index = settingMenuItemPathList.indexOf(pathname)
+ if (index !== -1) {
+ navigate(settingMenuItemPathList[index === 0 ? settingMenuItemPathList.length - 1 : index - 1])
+ }
+ })
- const isRoute = (path: string): string => (pathname.startsWith(path) ? 'active' : '')
+ useShortcut('switch_to_next_main_tab', () => {
+ const index = settingMenuItemPathList.indexOf(pathname)
+ if (index !== -1) {
+ navigate(settingMenuItemPathList[index === settingMenuItemPathList.length - 1 ? 0 : index + 1])
+ }
+ })
return (
diff --git a/src/renderer/src/pages/translate/TranslatePage.tsx b/src/renderer/src/pages/translate/TranslatePage.tsx
index f82cdc0fd4..2f26582288 100644
--- a/src/renderer/src/pages/translate/TranslatePage.tsx
+++ b/src/renderer/src/pages/translate/TranslatePage.tsx
@@ -16,7 +16,7 @@ import { fetchTranslate } from '@renderer/services/ApiService'
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
import { Assistant, Message, TranslateHistory } from '@renderer/types'
import { runAsyncFunction, uuid } from '@renderer/utils'
-import { Button, Dropdown, Empty, Flex, Popconfirm, Select, Space, Tooltip } from 'antd'
+import { Button, Dropdown, Empty, Flex, Popconfirm, Select, Space } from 'antd'
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
import dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks'
@@ -139,13 +139,6 @@ const TranslatePage: FC = () => {
})
}, [])
- const onKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
- e.preventDefault()
- onTranslate()
- }
- }
-
const SettingButton = () => {
if (isLocalAi) {
return null
@@ -190,11 +183,13 @@ const TranslatePage: FC = () => {
{t('translate.history.title')}
- {!isEmpty(translateHistory) && (
+ {translateHistory?.length && (
+ onConfirm={clearHistory}
+ okText="Yes"
+ cancelText="No">
}>
{t('translate.history.clear')}
@@ -249,25 +244,14 @@ const TranslatePage: FC = () => {
-
- Enter: {t('translate.button.translate')}
-
- Shift + Enter: {t('translate.tooltip.newline')}
-
- }>
- }>
- {t('translate.button.translate')}
-
-
+ }>
+ {t('translate.button.translate')}
+