diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index f5bcfe88f9..1ae16d5556 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -63,7 +63,7 @@ export class WindowService { titleBarOverlay: nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight, backgroundColor: isMac ? undefined : nativeTheme.shouldUseDarkColors ? '#181818' : '#FFFFFF', darkTheme: nativeTheme.shouldUseDarkColors, - trafficLightPosition: { x: 8, y: 12 }, + trafficLightPosition: { x: 15, y: 12 }, ...(isLinux ? { icon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.js'), diff --git a/src/main/services/urlschema/mcp-install.ts b/src/main/services/urlschema/mcp-install.ts index e5f0a76501..3131830a16 100644 --- a/src/main/services/urlschema/mcp-install.ts +++ b/src/main/services/urlschema/mcp-install.ts @@ -65,7 +65,7 @@ export function handleMcpProtocolUrl(url: URL) { const mainWindow = windowService.getMainWindow() if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.webContents.executeJavaScript("window.navigate('/settings/mcp')") + mainWindow.webContents.executeJavaScript("window.navigate('/mcp-servers')") } break } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index b46910cd65..102e61c628 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -5,7 +5,7 @@ import { Provider } from 'react-redux' import { HashRouter, Route, Routes } from 'react-router-dom' import { PersistGate } from 'redux-persist/integration/react' -import Sidebar from './components/app/Sidebar' +import MainSidebar from './components/app/MainSidebar' import TopViewContainer from './components/TopView' import AntdProvider from './context/AntdProvider' import { CodeStyleProvider } from './context/CodeStyleProvider' @@ -18,6 +18,7 @@ import AppsPage from './pages/apps/AppsPage' import FilesPage from './pages/files/FilesPage' import HomePage from './pages/home/HomePage' import KnowledgePage from './pages/knowledge/KnowledgePage' +import McpServersPage from './pages/mcp-servers' import PaintingsRoutePage from './pages/paintings/PaintingsRoutePage' import SettingsPage from './pages/settings/SettingsPage' import TranslatePage from './pages/translate/TranslatePage' @@ -34,7 +35,7 @@ function App(): React.ReactElement { - + } /> } /> @@ -43,6 +44,7 @@ function App(): React.ReactElement { } /> } /> } /> + } /> } /> diff --git a/src/renderer/src/assets/styles/ant.scss b/src/renderer/src/assets/styles/ant.scss index d4788439e5..f311510c0c 100644 --- a/src/renderer/src/assets/styles/ant.scss +++ b/src/renderer/src/assets/styles/ant.scss @@ -25,7 +25,6 @@ } .minapp-drawer { - max-width: calc(100vw - var(--sidebar-width)); .ant-drawer-content-wrapper { box-shadow: none; } @@ -33,7 +32,7 @@ position: absolute; -webkit-app-region: drag; min-height: calc(var(--navbar-height) + 0.5px); - width: calc(100vw - var(--sidebar-width)); + width: 100%; margin-top: -0.5px; border-bottom: none; } diff --git a/src/renderer/src/assets/styles/color.scss b/src/renderer/src/assets/styles/color.scss index 6100e1d0ee..e5a7930a11 100644 --- a/src/renderer/src/assets/styles/color.scss +++ b/src/renderer/src/assets/styles/color.scss @@ -29,7 +29,7 @@ --color-text-secondary: rgba(235, 235, 245, 0.7); --color-icon: #ffffff99; --color-icon-white: #ffffff; - --color-border: #ffffff19; + --color-border: #000; --color-border-soft: #ffffff10; --color-border-mute: #ffffff05; --color-error: #f44336; @@ -44,8 +44,8 @@ --color-reference-text: #ffffff; --color-reference-background: #0b0e12; - --color-list-item: #222; - --color-list-item-hover: #1e1e1e; + --color-list-item: rgba(255, 255, 255, 0.1); + --color-list-item-hover: rgba(255, 255, 255, 0.05); --modal-background: #1f1f1f; @@ -56,7 +56,7 @@ --navbar-background-mac: rgba(20, 20, 20, 0.55); --navbar-background: #1f1f1f; - --navbar-height: 40px; + --navbar-height: 45px; --sidebar-width: 50px; --status-bar-height: 40px; --input-bar-height: 100px; @@ -71,7 +71,8 @@ --chat-background-assistant: #2c2c2c; --chat-text-user: var(--color-black); - --list-item-border-radius: 20px; + --list-item-border-radius: 8px; + --border-width: 0.5px; } [theme-mode='light'] { @@ -120,8 +121,8 @@ --color-reference-text: #000000; --color-reference-background: #f1f7ff; - --color-list-item: #eee; - --color-list-item-hover: #f5f5f5; + --color-list-item: rgba(255, 255, 255, 0.9); + --color-list-item-hover: rgba(255, 255, 255, 0.5); --modal-background: var(--color-white); @@ -136,4 +137,6 @@ --chat-background-user: #95ec69; --chat-background-assistant: #ffffff; --chat-text-user: var(--color-text); + + --border-width: 0.5px; } diff --git a/src/renderer/src/assets/styles/container.scss b/src/renderer/src/assets/styles/container.scss index ab5e8a7de8..c20e181060 100644 --- a/src/renderer/src/assets/styles/container.scss +++ b/src/renderer/src/assets/styles/container.scss @@ -1,8 +1,6 @@ #content-container { background-color: var(--color-background); border-top: 0.5px solid var(--color-border); - border-top-left-radius: 10px; - border-left: 0.5px solid var(--color-border); } .group-container { diff --git a/src/renderer/src/components/MinApp/MinappPopupContainer.tsx b/src/renderer/src/components/MinApp/MinappPopupContainer.tsx index bd360c8a30..252c25fe14 100644 --- a/src/renderer/src/components/MinApp/MinappPopupContainer.tsx +++ b/src/renderer/src/components/MinApp/MinappPopupContainer.tsx @@ -395,10 +395,7 @@ const MinappPopupContainer: React.FC = () => { height={'100%'} maskClosable={false} closeIcon={null} - style={{ - marginLeft: 'var(--sidebar-width)', - backgroundColor: window.root.style.background - }}> + style={{ backgroundColor: window.root.style.background }}> {!isReady && ( = () => { + return ( + +
+ + EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)} style={{ marginRight: 5 }}> + + + +
+ ) +} + +const Container = styled.div` + display: flex; + width: var(--assistant-width); + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0; + padding-left: var(--sidebar-width); + height: var(--navbar-height); + min-height: var(--navbar-height); + background-color: transparent; + -webkit-app-region: drag; +` + +export const NavbarIcon = styled.div` + -webkit-app-region: none; + border-radius: 8px; + height: 30px; + padding: 0 7px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + transition: all 0.2s ease-in-out; + -webkit-app-region: no-drag; + cursor: pointer; + .iconfont { + font-size: 18px; + color: var(--color-icon); + &.icon-a-addchat { + font-size: 20px; + } + &.icon-a-darkmode { + font-size: 20px; + } + &.icon-appstore { + font-size: 20px; + } + } + .anticon { + color: var(--color-icon); + font-size: 16px; + } + &:hover { + background-color: var(--color-background-mute); + color: var(--color-icon-white); + } +` + +export default HeaderNavbar diff --git a/src/renderer/src/components/app/MainSidebar.tsx b/src/renderer/src/components/app/MainSidebar.tsx new file mode 100644 index 0000000000..bc8a1a640b --- /dev/null +++ b/src/renderer/src/components/app/MainSidebar.tsx @@ -0,0 +1,378 @@ +import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar' +import { UserAvatar } from '@renderer/config/env' +import { useTheme } from '@renderer/context/ThemeProvider' +import { useAssistants } from '@renderer/hooks/useAssistant' +import useAvatar from '@renderer/hooks/useAvatar' +import { useChat } from '@renderer/hooks/useChat' +import { useSettings } from '@renderer/hooks/useSettings' +import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' +import NavigationService from '@renderer/services/NavigationService' +import { ThemeMode } from '@renderer/types' +import { isEmoji } from '@renderer/utils' +import { Avatar, Tooltip } from 'antd' +import { + Blocks, + Bot, + ChevronDown, + ChevronRight, + FileSearch, + Folder, + Languages, + LayoutGrid, + MessageSquare, + Moon, + Palette, + Sparkle, + SquareTerminal, + Sun, + SunMoon +} from 'lucide-react' +import { FC, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useLocation, useNavigate } from 'react-router-dom' +import styled from 'styled-components' + +import Tabs from '../../pages/home/Tabs' +import MainNavbar from './MainNavbar' + +type Tab = 'assistants' | 'topic' | 'settings' + +const MainSidebar: FC = () => { + const { assistants } = useAssistants() + const navigate = useNavigate() + const [tab, setTab] = useState('assistants') + const avatar = useAvatar() + const { userName, defaultPaintingProvider } = useSettings() + const { t } = useTranslation() + const { theme, settedTheme, toggleTheme } = useTheme() + const [isAppMenuExpanded, setIsAppMenuExpanded] = useState(false) + + const location = useLocation() + const state = location.state + const { pathname } = location + + const { activeAssistant, activeTopic, setActiveAssistant, setActiveTopic } = useChat() + const { showAssistants, showTopics, topicPosition } = useSettings() + + useEffect(() => { + NavigationService.setNavigate(navigate) + }, [navigate]) + + useEffect(() => { + state?.assistant && setActiveAssistant(state?.assistant) + state?.topic && setActiveTopic(state?.topic) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state]) + + useEffect(() => { + const unsubscribe = EventEmitter.on(EVENT_NAMES.SWITCH_ASSISTANT, (assistantId: string) => { + const newAssistant = assistants.find((a) => a.id === assistantId) + if (newAssistant) { + setActiveAssistant(newAssistant) + } + }) + + return () => { + unsubscribe() + } + }, [assistants, setActiveAssistant]) + + useEffect(() => { + const canMinimize = topicPosition == 'left' ? !showAssistants : !showAssistants && !showTopics + window.api.window.setMinimumSize(canMinimize ? 520 : 1080, 600) + + return () => { + window.api.window.resetMinimumSize() + } + }, [showAssistants, showTopics, topicPosition]) + + const onAvatarClick = () => { + navigate('/settings/provider') + } + + const onChageTheme = (e: React.MouseEvent) => { + e.stopPropagation() + toggleTheme() + } + + const appMenuItems = [ + { icon: , text: t('agents.title'), path: '/agents' }, + { icon: , text: t('translate.title'), path: '/translate' }, + { + icon: , + text: t('paintings.title'), + path: `/paintings/${defaultPaintingProvider}` + }, + { icon: , text: t('minapp.title'), path: '/apps' }, + { icon: , text: t('knowledge.title'), path: '/knowledge' }, + { icon: , text: t('common.mcp'), path: '/mcp-servers' }, + { icon: , text: t('files.title'), path: '/files' } + ] + + const isRoutes = (path: string): boolean => pathname.startsWith(path) + + if (location.pathname !== '/') { + return null + } + + const onChageTab = (tab: Tab) => { + setTab(tab) + setIsAppMenuExpanded(false) + } + + if (!showAssistants) { + return null + } + + return ( + + + + onChageTab('assistants')}> + + + + + {t('assistants.title')} + + {tab === 'topic' && ( + + {activeAssistant.name} + + )} + + onChageTab('topic')}> + + + + + {t('common.topics')} + + + setIsAppMenuExpanded(!isAppMenuExpanded)}> + + + + + {t('common.apps')} + + + {isAppMenuExpanded ? ( + + ) : ( + + )} + + + {isAppMenuExpanded && ( + + {appMenuItems.map((item) => ( + navigate(item.path)}> + + {item.icon} + {item.text} + + + ))} + + )} + + + + + {isEmoji(avatar) ? ( + + {avatar} + + ) : ( + + )} + {userName} + + + + {settedTheme === ThemeMode.dark ? ( + + ) : settedTheme === ThemeMode.light ? ( + + ) : ( + + )} + + + + + ) +} + +const Container = styled.div` + display: flex; + flex-direction: column; + width: var(--assistant-width); + max-width: var(--assistant-width); + border-right: 0.5px solid var(--color-border); +` + +const MainMenu = styled.div` + display: flex; + flex-direction: column; + padding: 10px; + padding-top: 0; + gap: 8px; +` + +const MainMenuItem = styled.div<{ active?: boolean }>` + display: flex; + justify-content: space-between; + align-items: center; + gap: 5px; + background-color: ${({ active }) => (active ? 'var(--color-list-item)' : 'transparent')}; + padding: 5px 10px; + border-radius: 5px; + border-radius: 8px; + &:hover { + background-color: ${({ active }) => (active ? 'var(--color-list-item)' : 'var(--color-list-item-hover)')}; + } +` + +const MainMenuItemLeft = styled.div` + display: flex; + align-items: center; + gap: 5px; +` + +const MainMenuItemRight = styled.div` + display: flex; + align-items: center; + gap: 5px; +` + +const MainMenuItemRightText = styled.div` + font-size: 12px; + color: var(--color-text-3); +` + +const MainMenuItemIcon = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; +` + +const MainMenuItemText = styled.div` + font-size: 14px; + font-weight: 500; +` + +const UserMenu = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin: 10px; + margin-bottom: 10px; + padding: 4px 8px; + gap: 5px; + + border-radius: 8px; + &:hover { + background-color: var(--color-list-item); + } +` + +const UserMenuLeft = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +` + +const AvatarImg = styled(Avatar)` + width: 28px; + height: 28px; + background-color: var(--color-background-soft); + border: none; + cursor: pointer; +` + +const UserMenuText = styled.div` + font-size: 14px; + font-weight: 500; +` + +const Icon = styled.div<{ theme: string }>` + width: 30px; + height: 30px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + box-sizing: border-box; + -webkit-app-region: none; + border: 0.5px solid transparent; + &:hover { + background-color: ${({ theme }) => (theme === 'dark' ? 'var(--color-black)' : 'var(--color-white)')}; + opacity: 0.8; + cursor: pointer; + .icon { + color: var(--color-icon-white); + } + } + &.active { + background-color: ${({ theme }) => (theme === 'dark' ? 'var(--color-black)' : 'var(--color-white)')}; + border: 0.5px solid var(--color-border); + .icon { + color: var(--color-primary); + } + } + + @keyframes borderBreath { + 0% { + opacity: 0.1; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.1; + } + } + + &.opened-minapp { + position: relative; + } + &.opened-minapp::after { + content: ''; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + border-radius: inherit; + opacity: 0.3; + border: 0.5px solid var(--color-primary); + } +` + +const SubMenu = styled.div` + display: flex; + flex-direction: column; + gap: 5px; +` + +export default MainSidebar diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index e6176c2a00..7dfe28452a 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -1,24 +1,16 @@ import { isLinux, isMac, isWindows } from '@renderer/config/constant' import { useFullscreen } from '@renderer/hooks/useFullscreen' -import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor' +import { Button } from 'antd' +import { X } from 'lucide-react' import type { FC, PropsWithChildren } from 'react' import type { HTMLAttributes } from 'react' +import { useNavigate } from 'react-router-dom' import styled from 'styled-components' type Props = PropsWithChildren & HTMLAttributes export const Navbar: FC = ({ children, ...props }) => { - const backgroundColor = useNavBackgroundColor() - - return ( - - {children} - - ) -} - -export const NavbarLeft: FC = ({ children, ...props }) => { - return {children} + return {children} } export const NavbarCenter: FC = ({ children, ...props }) => { @@ -36,9 +28,12 @@ export const NavbarRight: FC = ({ children, ...props }) => { export const NavbarMain: FC = ({ children, ...props }) => { const isFullscreen = useFullscreen() + const navigate = useNavigate() + return ( {children} + ) } @@ -49,28 +44,8 @@ const NavbarContainer = styled.div` flex-direction: row; min-height: var(--navbar-height); max-height: var(--navbar-height); - margin-left: ${isMac ? 'calc(var(--sidebar-width) * -1)' : 0}; - padding-left: ${isMac ? 'var(--sidebar-width)' : 0}; -webkit-app-region: drag; -` - -const NavbarLeftContainer = styled.div` - min-width: var(--assistants-width); - padding: 0 10px; - display: flex; - flex-direction: row; - align-items: center; - font-weight: bold; - color: var(--color-text-1); -` - -const NavbarCenterContainer = styled.div` - flex: 1; - display: flex; - align-items: center; - padding: 0 ${isMac ? '20px' : 0}; - font-weight: bold; - color: var(--color-text-1); + background-color: var(--color-background); ` const NavbarRightContainer = styled.div<{ $isFullscreen: boolean }>` @@ -87,9 +62,26 @@ const NavbarMainContainer = styled.div<{ $isFullscreen: boolean }>` display: flex; flex-direction: row; align-items: center; + height: var(--navbar-height); + max-height: var(--navbar-height); + min-height: var(--navbar-height); justify-content: space-between; - padding: 0 ${isMac ? '20px' : 0}; + padding-left: ${isMac ? '70px' : '10px'}; font-weight: bold; color: var(--color-text-1); padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWindows ? '140px' : isLinux ? '120px' : '12px')}; + -webkit-app-region: drag; +` + +const NavbarCenterContainer = styled.div` + flex: 1; + display: flex; + align-items: center; + height: var(--navbar-height); + max-height: var(--navbar-height); + min-height: var(--navbar-height); + padding: 0 8px; + font-weight: bold; + justify-content: space-between; + color: var(--color-text-1); ` diff --git a/src/renderer/src/hooks/useChat.tsx b/src/renderer/src/hooks/useChat.tsx new file mode 100644 index 0000000000..5c92759e3e --- /dev/null +++ b/src/renderer/src/hooks/useChat.tsx @@ -0,0 +1,42 @@ +import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' +import { useAppDispatch, useAppSelector } from '@renderer/store' +import { setActiveAssistant, setActiveTopic } from '@renderer/store/runtime' +import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk' +import { Assistant } from '@renderer/types' +import { Topic } from '@renderer/types' +import { find } from 'lodash' +import { useEffect } from 'react' + +import { useAssistants } from './useAssistant' + +export const useChat = () => { + const { assistants } = useAssistants() + const activeAssistant = useAppSelector((state) => state.runtime.chat.activeAssistant) || assistants[0] + const activeTopic = useAppSelector((state) => state.runtime.chat.activeTopic) || activeAssistant?.topics[0]! + const dispatch = useAppDispatch() + + useEffect(() => { + if (activeTopic) { + dispatch(loadTopicMessagesThunk(activeTopic.id)) + EventEmitter.emit(EVENT_NAMES.CHANGE_TOPIC, activeTopic) + } + }, [activeTopic, dispatch]) + + useEffect(() => { + // activeTopic not in assistant.topics + if (activeAssistant && !find(activeAssistant.topics, { id: activeTopic?.id })) { + dispatch(setActiveTopic(activeAssistant.topics[0])) + } + }, [activeTopic?.id, activeAssistant, dispatch]) + + return { + activeAssistant, + activeTopic, + setActiveAssistant: (assistant: Assistant) => { + dispatch(setActiveAssistant(assistant)) + }, + setActiveTopic: (topic: Topic) => { + dispatch(setActiveTopic(topic)) + } + } +} diff --git a/src/renderer/src/hooks/useTopic.ts b/src/renderer/src/hooks/useTopic.ts index d2ba9cacbe..72e96c069b 100644 --- a/src/renderer/src/hooks/useTopic.ts +++ b/src/renderer/src/hooks/useTopic.ts @@ -1,47 +1,16 @@ import db from '@renderer/databases' import i18n from '@renderer/i18n' -import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { deleteMessageFiles } from '@renderer/services/MessagesService' import store from '@renderer/store' import { updateTopic } from '@renderer/store/assistants' -import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk' import { Assistant, Topic } from '@renderer/types' import { findMainTextBlocks } from '@renderer/utils/messageUtils/find' -import { find, isEmpty } from 'lodash' -import { useEffect, useState } from 'react' +import { isEmpty } from 'lodash' -import { useAssistant } from './useAssistant' import { getStoreSetting } from './useSettings' const renamingTopics = new Set() -let _activeTopic: Topic -let _setActiveTopic: (topic: Topic) => void - -export function useActiveTopic(_assistant: Assistant, topic?: Topic) { - const { assistant } = useAssistant(_assistant.id) - const [activeTopic, setActiveTopic] = useState(topic || _activeTopic || assistant?.topics[0]) - - _activeTopic = activeTopic - _setActiveTopic = setActiveTopic - - useEffect(() => { - if (activeTopic) { - store.dispatch(loadTopicMessagesThunk(activeTopic.id)) - EventEmitter.emit(EVENT_NAMES.CHANGE_TOPIC, activeTopic) - } - }, [activeTopic]) - - useEffect(() => { - // activeTopic not in assistant.topics - if (assistant && !find(assistant.topics, { id: activeTopic?.id })) { - setActiveTopic(assistant.topics[0]) - } - }, [activeTopic?.id, assistant]) - - return { activeTopic, setActiveTopic } -} - export function useTopic(assistant: Assistant, topicId?: string) { return assistant?.topics.find((topic) => topic.id === topicId) } @@ -86,7 +55,6 @@ export const autoRenameTopic = async (assistant: Assistant, topicId: string) => .substring(0, 50) if (topicName) { const data = { ...topic, name: topicName } as Topic - _setActiveTopic(data) store.dispatch(updateTopic({ assistantId: assistant.id, topic: data })) } return @@ -97,7 +65,6 @@ export const autoRenameTopic = async (assistant: Assistant, topicId: string) => const summaryText = await fetchMessagesSummary({ messages: topic.messages, assistant }) if (summaryText) { const data = { ...topic, name: summaryText } - _setActiveTopic(data) store.dispatch(updateTopic({ assistantId: assistant.id, topic: data })) } else { window.message?.error(i18n.t('message.error.fetchTopicName')) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index ecd278585d..cbffdebcc3 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -421,7 +421,9 @@ "pinyin.asc": "Sort by Pinyin (A-Z)", "pinyin.desc": "Sort by Pinyin (Z-A)" }, - "no_results": "No results" + "no_results": "No results", + "apps": "Apps", + "mcp": "Tools" }, "docs": { "title": "Docs" diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 99e29e6a8e..beae00fa3a 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -421,7 +421,9 @@ "pinyin.asc": "ピンインで昇順ソート", "pinyin.desc": "ピンインで降順ソート" }, - "no_results": "検索結果なし" + "no_results": "検索結果なし", + "apps": "アプリ", + "mcp": "ツール" }, "docs": { "title": "ドキュメント" diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 5a5308ed2c..b770cba77c 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -421,7 +421,9 @@ "pinyin.asc": "Сортировать по пиньинь (А-Я)", "pinyin.desc": "Сортировать по пиньинь (Я-А)" }, - "no_results": "Результатов не найдено" + "no_results": "Результатов не найдено", + "apps": "Приложения", + "mcp": "Инструменты" }, "docs": { "title": "Документация" diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 3b8ab324c2..27bbb8b41f 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -421,7 +421,9 @@ "pinyin.asc": "按拼音升序", "pinyin.desc": "按拼音降序" }, - "no_results": "无结果" + "no_results": "无结果", + "apps": "应用", + "mcp": "工具" }, "docs": { "title": "帮助文档" diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 05a658b74d..68857301cb 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -421,7 +421,9 @@ "pinyin.asc": "按拼音升序", "pinyin.desc": "按拼音降序" }, - "no_results": "沒有結果" + "no_results": "沒有結果", + "apps": "應用", + "mcp": "工具" }, "docs": { "title": "說明文件" diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index b9873c310b..cd64694e1b 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -1,5 +1,5 @@ import { ImportOutlined, PlusOutlined } from '@ant-design/icons' -import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' +import { NavbarCenter, NavbarMain } from '@renderer/components/app/Navbar' import CustomTag from '@renderer/components/CustomTag' import ListItem from '@renderer/components/ListItem' import Scrollbar from '@renderer/components/Scrollbar' @@ -152,7 +152,7 @@ const AgentsPage: FC = () => { return ( - + {t('agents.title')} { />
- +
diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index 31cb3f2392..39a3c063d2 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -1,7 +1,7 @@ -import { Navbar, NavbarMain } from '@renderer/components/app/Navbar' +import { NavbarCenter, NavbarMain } from '@renderer/components/app/Navbar' import { useMinapps } from '@renderer/hooks/useMinapps' import { Button, Input } from 'antd' -import { Search, SettingsIcon, X } from 'lucide-react' +import { Search, SettingsIcon } from 'lucide-react' import React, { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useLocation } from 'react-router' @@ -41,8 +41,8 @@ const AppsPage: FC = () => { return ( - - + + {t('minapp.title')} { style={{ width: '30%', height: 28, - borderRadius: 15, - position: 'absolute', - left: '50vw', - transform: 'translateX(-50%)' + borderRadius: 15 }} size="small" variant="filled" @@ -65,11 +62,11 @@ const AppsPage: FC = () => {
- {topicPosition === 'right' && showTopics && ( - - )} ) } diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index 3bc47468fd..5d46ee9219 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -1,18 +1,14 @@ import { useAssistants } from '@renderer/hooks/useAssistant' +import { useChat } from '@renderer/hooks/useChat' import { useSettings } from '@renderer/hooks/useSettings' -import { useActiveTopic } from '@renderer/hooks/useTopic' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import NavigationService from '@renderer/services/NavigationService' -import { Assistant } from '@renderer/types' -import { FC, useEffect, useState } from 'react' +import { FC, useEffect } from 'react' import { useLocation, useNavigate } from 'react-router-dom' import styled from 'styled-components' import Chat from './Chat' import Navbar from './Navbar' -import HomeTabs from './Tabs' - -let _activeAssistant: Assistant const HomePage: FC = () => { const { assistants } = useAssistants() @@ -21,12 +17,9 @@ const HomePage: FC = () => { const location = useLocation() const state = location.state - const [activeAssistant, setActiveAssistant] = useState(state?.assistant || _activeAssistant || assistants[0]) - const { activeTopic, setActiveTopic } = useActiveTopic(activeAssistant, state?.topic) + const { activeAssistant, activeTopic, setActiveAssistant, setActiveTopic } = useChat() const { showAssistants, showTopics, topicPosition } = useSettings() - _activeAssistant = activeAssistant - useEffect(() => { NavigationService.setNavigate(navigate) }, [navigate]) @@ -69,15 +62,6 @@ const HomePage: FC = () => { position="left" /> - {showAssistants && ( - - )} = ({ ref, setInputValue, resizeTextArea, Toolbar newList.push({ label: t('settings.mcp.addServer') + '...', icon: , - action: () => navigate('/settings/mcp') + action: () => navigate('/mcp-servers') }) newList.unshift({ diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index 6fecd2df78..d91faf7e11 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -257,7 +257,7 @@ const MessageFooter = styled.div` align-items: center; padding: 2px 0; margin-top: 2px; - border-top: 1px dotted var(--color-border); + border-top: 0.5px dotted var(--color-border); gap: 20px; ` diff --git a/src/renderer/src/pages/home/Navbar.tsx b/src/renderer/src/pages/home/Navbar.tsx index c9e2797a8b..858b47672a 100644 --- a/src/renderer/src/pages/home/Navbar.tsx +++ b/src/renderer/src/pages/home/Navbar.tsx @@ -1,9 +1,9 @@ -import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar' +import { Navbar } from '@renderer/components/app/Navbar' import { HStack } from '@renderer/components/Layout' import FloatingSidebar from '@renderer/components/Popups/FloatingSidebar' import MinAppsPopover from '@renderer/components/Popups/MinAppsPopover' import SearchPopup from '@renderer/components/Popups/SearchPopup' -import { isMac } from '@renderer/config/constant' +import { isLinux, isMac, isWindows } from '@renderer/config/constant' import { useAssistant } from '@renderer/hooks/useAssistant' import { useFullscreen } from '@renderer/hooks/useFullscreen' import { modelGenerating } from '@renderer/hooks/useRuntime' @@ -16,7 +16,7 @@ import { setNarrowMode } from '@renderer/store/settings' import { Assistant, Topic } from '@renderer/types' import { Tooltip } from 'antd' import { t } from 'i18next' -import { LayoutGrid, MessageSquareDiff, PanelLeftClose, PanelRightClose, Search } from 'lucide-react' +import { LayoutGrid, PanelLeftClose, PanelRightClose, Search } from 'lucide-react' import { FC, useCallback, useState } from 'react' import styled from 'styled-components' @@ -54,6 +54,7 @@ const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTo toggleShowAssistants() } }, [showAssistants, toggleShowAssistants]) + const handleToggleShowTopics = useCallback(() => { if (showTopics) { // When hiding sidebar, set cooldown @@ -89,48 +90,13 @@ const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTo return ( - {showAssistants && ( - - - - - - - - EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)} style={{ marginRight: 5 }}> - - - - - )} - + - {!showAssistants && !sidebarHideCooldown && ( - - - toggleShowAssistants()} - style={{ marginRight: 8, marginLeft: isMac && !isFullscreen ? 4 : -12 }}> - - - - - )} - {!showAssistants && sidebarHideCooldown && ( - - toggleShowAssistants()} - style={{ marginRight: 8, marginLeft: isMac && !isFullscreen ? 4 : -12 }} - onMouseOut={() => setSidebarHideCooldown(false)}> - - - - )} + toggleShowAssistants()} + style={{ marginRight: 8, marginLeft: isMac && !isFullscreen ? 4 : -12 }}> + {showAssistants ? : } + @@ -183,11 +149,27 @@ const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTo )} - + ) } +const NavbarContainer = styled.div<{ $isFullscreen: boolean; $showSidebar: boolean }>` + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + height: var(--navbar-height); + max-height: var(--navbar-height); + min-height: var(--navbar-height); + justify-content: space-between; + padding-left: ${({ $showSidebar }) => (isMac && !$showSidebar ? '70px' : '10px')}; + font-weight: bold; + color: var(--color-text-1); + padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWindows ? '140px' : isLinux ? '120px' : '12px')}; + -webkit-app-region: drag; +` + export const NavbarIcon = styled.div` -webkit-app-region: none; border-radius: 8px; diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index cbcd272791..56ce12e38d 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -6,7 +6,7 @@ import { useAssistants } from '@renderer/hooks/useAssistant' import { useAssistantsTabSortType } from '@renderer/hooks/useStore' import { useTags } from '@renderer/hooks/useTags' import { Assistant, AssistantsSortType } from '@renderer/types' -import { Divider, Tooltip } from 'antd' +import { Tooltip } from 'antd' import { FC, useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -80,7 +80,7 @@ const Assistants: FC = ({ if (assistantsTabSortType === 'tags') { return ( -
+
{getGroupedAssistants.map((group) => ( {group.tag !== t('assistants.tags.untagged') && ( @@ -95,7 +95,7 @@ const Assistants: FC = ({ {group.tag} - + )} {!collapsedTags[group.tag] && ( @@ -207,13 +207,15 @@ const AssistantAddItem = styled.div` ` const GroupTitle = styled.div` - padding: 8px 0; - position: relative; color: var(--color-text-2); font-size: 12px; font-weight: 500; - margin-bottom: -8px; cursor: pointer; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 24px; ` const GroupTitleName = styled.div` @@ -221,13 +223,18 @@ const GroupTitleName = styled.div` text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - background-color: var(--color-background); box-sizing: border-box; padding: 0 4px; color: var(--color-text); - position: absolute; - transform: translateY(2px); font-size: 13px; + line-height: 24px; + margin-right: 2px; + display: flex; +` + +const GroupTitleDivider = styled.div` + flex: 1; + border-top: 1px solid var(--color-border); ` const AssistantName = styled.div` diff --git a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx index 518473bb14..6bf23dfe1f 100644 --- a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx @@ -20,11 +20,11 @@ import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings' import { getDefaultModel, getDefaultTopic } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { Assistant, AssistantsSortType } from '@renderer/types' -import { getLeadingEmoji, uuid } from '@renderer/utils' +import { classNames, getLeadingEmoji, uuid } from '@renderer/utils' import { hasTopicPendingRequests } from '@renderer/utils/queue' -import { Dropdown, MenuProps } from 'antd' +import { Button, Dropdown, MenuProps } from 'antd' import { omit } from 'lodash' -import { AlignJustify, Plus, Settings2, Tag, Tags } from 'lucide-react' +import { AlignJustify, EllipsisVertical, Plus, Settings2, Tag, Tags } from 'lucide-react' import { FC, memo, startTransition, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -63,6 +63,7 @@ const AssistantItem: FC = ({ const { assistants, updateAssistants } = useAssistants() const [isPending, setIsPending] = useState(false) + const [isMenuOpen, setIsMenuOpen] = useState(false) useEffect(() => { if (isActive) { @@ -141,7 +142,7 @@ const AssistantItem: FC = ({ return ( - + {assistantIconType === 'model' ? ( = ({ )} {assistantName} - {isActive && ( - EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}> - {assistant.topics.length} - - )} + + )} - + diff --git a/src/renderer/src/pages/paintings/DmxapiPage.tsx b/src/renderer/src/pages/paintings/DmxapiPage.tsx index 64fe53904f..7acf718d47 100644 --- a/src/renderer/src/pages/paintings/DmxapiPage.tsx +++ b/src/renderer/src/pages/paintings/DmxapiPage.tsx @@ -1,6 +1,6 @@ import { PlusOutlined, RedoOutlined } from '@ant-design/icons' import DMXAPIToImg from '@renderer/assets/images/providers/DMXAPI-to-img.webp' -import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' +import { NavbarCenter, NavbarMain, NavbarRight } from '@renderer/components/app/Navbar' import { VStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout' import Scrollbar from '@renderer/components/Scrollbar' @@ -645,7 +645,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { return ( - + {t('paintings.title')} {isMac && ( @@ -658,7 +658,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { )} - + diff --git a/src/renderer/src/pages/paintings/SiliconPage.tsx b/src/renderer/src/pages/paintings/SiliconPage.tsx index 0a3648260b..b10e3048d7 100644 --- a/src/renderer/src/pages/paintings/SiliconPage.tsx +++ b/src/renderer/src/pages/paintings/SiliconPage.tsx @@ -5,7 +5,7 @@ import ImageSize3_2 from '@renderer/assets/images/paintings/image-size-3-2.svg' import ImageSize3_4 from '@renderer/assets/images/paintings/image-size-3-4.svg' import ImageSize9_16 from '@renderer/assets/images/paintings/image-size-9-16.svg' import ImageSize16_9 from '@renderer/assets/images/paintings/image-size-16-9.svg' -import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' +import { NavbarCenter, NavbarMain, NavbarRight } from '@renderer/components/app/Navbar' import { HStack, VStack } from '@renderer/components/Layout' import Scrollbar from '@renderer/components/Scrollbar' import TranslateButton from '@renderer/components/TranslateButton' @@ -354,7 +354,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { return ( - + {t('paintings.title')} {isMac && ( @@ -367,7 +367,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { )} - + {t('common.provider')} diff --git a/src/renderer/src/pages/paintings/TokenFluxPage.tsx b/src/renderer/src/pages/paintings/TokenFluxPage.tsx index 85a39df438..6688f98081 100644 --- a/src/renderer/src/pages/paintings/TokenFluxPage.tsx +++ b/src/renderer/src/pages/paintings/TokenFluxPage.tsx @@ -1,5 +1,5 @@ import { PlusOutlined } from '@ant-design/icons' -import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' +import { NavbarCenter, NavbarMain, NavbarRight } from '@renderer/components/app/Navbar' import Scrollbar from '@renderer/components/Scrollbar' import TranslateButton from '@renderer/components/TranslateButton' import { isMac } from '@renderer/config/constant' @@ -367,7 +367,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => { return ( - + {t('paintings.title')} {isMac && ( @@ -376,7 +376,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => { )} - + {/* Provider Section */} diff --git a/src/renderer/src/pages/settings/SettingsPage.tsx b/src/renderer/src/pages/settings/SettingsPage.tsx index 7ce087313c..6c84970b2a 100644 --- a/src/renderer/src/pages/settings/SettingsPage.tsx +++ b/src/renderer/src/pages/settings/SettingsPage.tsx @@ -1,4 +1,4 @@ -import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' +import { NavbarCenter, NavbarMain } from '@renderer/components/app/Navbar' import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings' import { Cloud, @@ -10,11 +10,9 @@ import { Package, Rocket, Settings2, - SquareTerminal, TextCursorInput, Zap } from 'lucide-react' -// 导入useAppSelector import { FC } from 'react' import { useTranslation } from 'react-i18next' import { Link, Route, Routes, useLocation } from 'react-router-dom' @@ -24,8 +22,6 @@ import AboutSettings from './AboutSettings' import DataSettings from './DataSettings/DataSettings' import DisplaySettings from './DisplaySettings/DisplaySettings' import GeneralSettings from './GeneralSettings' -import MCPSettings from './MCPSettings' -import { McpSettingsNavbar } from './MCPSettings/McpSettingsNavbar' import ProvidersList from './ProviderSettings' import QuickAssistantSettings from './QuickAssistantSettings' import QuickPhraseSettings from './QuickPhraseSettings' @@ -41,10 +37,9 @@ const SettingsPage: FC = () => { return ( - + {t('settings.title')} - {pathname.includes('/settings/mcp') && } - + @@ -65,12 +60,6 @@ const SettingsPage: FC = () => { {t('settings.websearch.title')} - - - - {t('settings.mcp.title')} - - @@ -125,7 +114,6 @@ const SettingsPage: FC = () => { } /> } /> } /> - } /> } /> } /> } /> diff --git a/src/renderer/src/pages/translate/TranslatePage.tsx b/src/renderer/src/pages/translate/TranslatePage.tsx index 7653aba898..dbce871488 100644 --- a/src/renderer/src/pages/translate/TranslatePage.tsx +++ b/src/renderer/src/pages/translate/TranslatePage.tsx @@ -1,5 +1,5 @@ import { CheckOutlined, DeleteOutlined, HistoryOutlined, SendOutlined } from '@ant-design/icons' -import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' +import { NavbarCenter, NavbarMain } from '@renderer/components/app/Navbar' import CopyIcon from '@renderer/components/Icons/CopyIcon' import { HStack } from '@renderer/components/Layout' import { isEmbeddingModel } from '@renderer/config/models' @@ -431,8 +431,8 @@ const TranslatePage: FC = () => { return ( - - + + {t('translate.title')}