From 077a66c675919aac32444a51c4dde4fe7c55bfee Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Thu, 24 Oct 2024 14:50:16 +0800 Subject: [PATCH] feat: scrollbar --- package.json | 1 + src/renderer/src/assets/styles/index.scss | 9 +- src/renderer/src/assets/styles/scrollbar.scss | 23 +- .../src/components/Scrollbar/index.tsx | 22 ++ src/renderer/src/hooks/useAppInit.ts | 1 - src/renderer/src/pages/agents/Agents.tsx | 80 ++-- src/renderer/src/pages/agents/AgentsPage.tsx | 56 +-- src/renderer/src/pages/files/FilesPage.tsx | 23 +- .../src/pages/home/Messages/Messages.tsx | 50 +-- .../src/pages/home/Tabs/Assistants.tsx | 113 +++--- src/renderer/src/pages/home/Tabs/Settings.tsx | 347 +++++++++--------- src/renderer/src/pages/home/Tabs/Topics.tsx | 68 ++-- .../pages/settings/ProviderSettings/index.tsx | 106 +++--- yarn.lock | 76 +++- 14 files changed, 542 insertions(+), 433 deletions(-) create mode 100644 src/renderer/src/components/Scrollbar/index.tsx diff --git a/package.json b/package.json index 40e9792d..1618f5b0 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "openai": "^4.52.1", "prettier": "^3.2.4", "react": "^18.2.0", + "react-custom-scrollbars-2": "^4.5.0", "react-dom": "^18.2.0", "react-i18next": "^14.1.2", "react-markdown": "^9.0.1", diff --git a/src/renderer/src/assets/styles/index.scss b/src/renderer/src/assets/styles/index.scss index 0cbff26b..9bb9b16f 100644 --- a/src/renderer/src/assets/styles/index.scss +++ b/src/renderer/src/assets/styles/index.scss @@ -1,5 +1,6 @@ @import './markdown.scss'; @import './ant.scss'; +@import './scrollbar.scss'; @import '../fonts/icon-fonts/iconfont.css'; @import '../fonts/ubuntu/ubuntu.css'; @@ -36,9 +37,6 @@ --color-error: #f44336; --color-link: #1677ff; --color-code-background: #323232; - --color-scrollbar-thumb: rgba(255, 255, 255, 0.08); - --color-scrollbar-thumb-hover: rgba(255, 255, 255, 0.15); - --color-scrollbar-thumb-active: rgba(255, 255, 255, 0.2); --color-hover: rgba(40, 40, 40, 1); --color-active: rgba(55, 55, 55, 1); @@ -48,7 +46,7 @@ --navbar-height: 40px; --sidebar-width: 50px; --status-bar-height: 40px; - --input-bar-height: 85px; + --input-bar-height: 100px; --assistants-width: 275px; --topic-list-width: 275px; @@ -88,9 +86,6 @@ body[theme-mode='light'] { --color-error: #f44336; --color-link: #1677ff; --color-code-background: #e3e3e3; - --color-scrollbar-thumb: rgba(0, 0, 0, 0.08); - --color-scrollbar-thumb-hover: rgba(0, 0, 0, 0.15); - --color-scrollbar-thumb-active: rgba(0, 0, 0, 0.2); --color-hover: var(--color-white-mute); --color-active: var(--color-white-soft); diff --git a/src/renderer/src/assets/styles/scrollbar.scss b/src/renderer/src/assets/styles/scrollbar.scss index c7549a91..1462c891 100644 --- a/src/renderer/src/assets/styles/scrollbar.scss +++ b/src/renderer/src/assets/styles/scrollbar.scss @@ -1,7 +1,17 @@ +:root { + --color-scrollbar-thumb: #6b6b6b; + --color-scrollbar-thumb-hover: #939393; +} + +body[theme-mode='light'] { + --color-scrollbar-thumb: #b1b1b1; + --color-scrollbar-thumb-hover: #7d7d7d; +} + /* 全局初始化滚动条样式 */ ::-webkit-scrollbar { - width: 4px; - height: 2px; + width: 5px; + height: 5px; } ::-webkit-scrollbar-track { @@ -9,8 +19,17 @@ } ::-webkit-scrollbar-thumb { + border-radius: 10px; background: var(--color-scrollbar-thumb); &:hover { background: var(--color-scrollbar-thumb-hover); } } + +pre::-webkit-scrollbar-thumb { + border-radius: 0; + background: rgba(0, 0, 0, 0.08); + &:hover { + background: rgba(0, 0, 0, 0.15); + } +} diff --git a/src/renderer/src/components/Scrollbar/index.tsx b/src/renderer/src/components/Scrollbar/index.tsx new file mode 100644 index 00000000..f8453e92 --- /dev/null +++ b/src/renderer/src/components/Scrollbar/index.tsx @@ -0,0 +1,22 @@ +import { ScrollbarProps, Scrollbars } from 'react-custom-scrollbars-2' +import styled from 'styled-components' + +export const Scrollbar: React.FC = ({ children, ...props }) => { + return ( + } + renderTrackHorizontal={(props) => }> + {children} + + ) +} + +const Thumb = styled.div` + border-radius: 10px; + background-color: var(--color-scrollbar-thumb); + &:hover { + background-color: var(--color-scrollbar-thumb-hover); + } +` diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index 57fd4f54..9293ef64 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -45,7 +45,6 @@ export function useAppInit() { useEffect(() => { const transparentWindow = windowStyle === 'transparent' && isMac && !minappShow window.root.style.background = transparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)' - !isMac && import('@renderer/assets/styles/scrollbar.scss') }, [windowStyle, minappShow]) useEffect(() => { diff --git a/src/renderer/src/pages/agents/Agents.tsx b/src/renderer/src/pages/agents/Agents.tsx index 40672ee7..d013c14c 100644 --- a/src/renderer/src/pages/agents/Agents.tsx +++ b/src/renderer/src/pages/agents/Agents.tsx @@ -2,6 +2,7 @@ import { DeleteOutlined, EditOutlined, MoreOutlined, PlusOutlined } from '@ant-d import AssistantSettingsPopup from '@renderer/components/AssistantSettings' import DragableList from '@renderer/components/DragableList' import { HStack } from '@renderer/components/Layout' +import { Scrollbar } from '@renderer/components/Scrollbar' import { useAgents } from '@renderer/hooks/useAgents' import { createAssistantFromAgent } from '@renderer/services/assistant' import { Agent } from '@renderer/types' @@ -56,42 +57,45 @@ const Agents: React.FC = ({ onClick }) => { ) return ( - - {agents.length > 0 && ( - setDragging(true)} - onDragEnd={() => setDragging(false)}> - {(agent: Agent) => ( - - onClick(agent)}> - - - {agent.emoji} {agent.name} - - e.stopPropagation()}> - - - - - - {agent.prompt} - - - )} - - )} - {!dragging && ( - - )} - + + + {agents.length > 0 && ( + setDragging(true)} + onDragEnd={() => setDragging(false)}> + {(agent: Agent) => ( + + onClick(agent)}> + + + {agent.emoji} {agent.name} + + e.stopPropagation()}> + + + + + + {agent.prompt} + + + )} + + )} + {!dragging && ( + + )} +
+ + ) } @@ -99,10 +103,8 @@ const Container = styled.div` padding: 15px; display: flex; flex-direction: column; - width: 280px; - height: calc(100vh - var(--navbar-height)); border-right: 0.5px solid var(--color-border); - overflow-y: scroll; + min-height: calc(100vh - var(--navbar-height)); ` const AgentItem = styled.div` diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index a2fa3dca..291020d7 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -1,5 +1,6 @@ import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { VStack } from '@renderer/components/Layout' +import { Scrollbar } from '@renderer/components/Scrollbar' import SystemAgents from '@renderer/config/agents.json' import { createAssistantFromAgent } from '@renderer/services/assistant' import { Agent } from '@renderer/types' @@ -60,32 +61,34 @@ const AgentsPage: FC = () => { - - - {Object.keys(agentGroups) - .reverse() - .map((group) => ( -
- - {group} - - - {agentGroups[group].map((agent, index) => { - return ( - - onAddAgentConfirm(getAgentFromSystemAgent(agent))} - agent={agent as any} - /> - - ) - })} - -
- ))} -
- - + + + + {Object.keys(agentGroups) + .reverse() + .map((group) => ( +
+ + {group} + + + {agentGroups[group].map((agent, index) => { + return ( + + onAddAgentConfirm(getAgentFromSystemAgent(agent))} + agent={agent as any} + /> + + ) + })} + +
+ ))} +
+ + + ) @@ -112,7 +115,6 @@ const AssistantsContainer = styled.div` flex-direction: row; height: calc(100vh - var(--navbar-height)); padding: 15px 20px; - overflow-y: scroll; ` const AgentPrompt = styled.div` diff --git a/src/renderer/src/pages/files/FilesPage.tsx b/src/renderer/src/pages/files/FilesPage.tsx index 207aeb51..f98e4ff9 100644 --- a/src/renderer/src/pages/files/FilesPage.tsx +++ b/src/renderer/src/pages/files/FilesPage.tsx @@ -1,5 +1,6 @@ import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { VStack } from '@renderer/components/Layout' +import { Scrollbar } from '@renderer/components/Scrollbar' import db from '@renderer/databases' import FileManager from '@renderer/services/file' import { FileType, FileTypes } from '@renderer/types' @@ -66,15 +67,17 @@ const FilesPage: FC = () => { {t('files.title')} - - - + + +
+ + ) @@ -93,8 +96,6 @@ const ContentContainer = styled.div` flex-direction: row; justify-content: center; height: 100%; - overflow-y: scroll; - padding: 15px; ` const FileNameText = styled.div` diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx index f9446c1b..51df86aa 100644 --- a/src/renderer/src/pages/home/Messages/Messages.tsx +++ b/src/renderer/src/pages/home/Messages/Messages.tsx @@ -1,4 +1,4 @@ -import { isWindows } from '@renderer/config/constant' +import { Scrollbar } from '@renderer/components/Scrollbar' import db from '@renderer/databases' import { useAssistant } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' @@ -9,7 +9,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { deleteMessageFiles, filterMessages, getContextCount } from '@renderer/services/messages' import { estimateHistoryTokens, estimateMessageUsage } from '@renderer/services/tokens' import { Assistant, Message, Model, Topic } from '@renderer/types' -import { captureScrollableDiv, classNames, runAsyncFunction, uuid } from '@renderer/utils' +import { captureScrollableDiv, runAsyncFunction, uuid } from '@renderer/utils' import { t } from 'i18next' import { flatten, last, reverse, take } from 'lodash' import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' @@ -220,45 +220,33 @@ const Messages: FC = ({ assistant, topic, setActiveTopic }) => { }, [assistant, messages]) return ( - - - {lastMessage && } - {reverse([...messages]).map((message, index) => ( - - ))} - - + + + + {lastMessage && } + {reverse([...messages]).map((message, index) => ( + + ))} + + + ) } const Container = styled.div` - position: relative; display: flex; - flex-direction: column; - overflow-y: auto; flex-direction: column-reverse; - max-height: calc(100vh - var(--input-bar-height) - var(--navbar-height)); padding: 10px 0; background-color: var(--color-background); padding-bottom: 20px; overflow-x: hidden; - &.scrollbar { - &::-webkit-scrollbar { - width: 10px; - } - } ` export default Messages diff --git a/src/renderer/src/pages/home/Tabs/Assistants.tsx b/src/renderer/src/pages/home/Tabs/Assistants.tsx index da342bf8..8d5733fd 100644 --- a/src/renderer/src/pages/home/Tabs/Assistants.tsx +++ b/src/renderer/src/pages/home/Tabs/Assistants.tsx @@ -2,6 +2,7 @@ import { DeleteOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined, SaveOu import AssistantSettingsPopup from '@renderer/components/AssistantSettings' import DragableList from '@renderer/components/DragableList' import CopyIcon from '@renderer/components/Icons/CopyIcon' +import { Scrollbar } from '@renderer/components/Scrollbar' import { useAgents } from '@renderer/hooks/useAgents' import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' @@ -178,69 +179,69 @@ const Assistants: FC = ({ }, [activeAssistant?.id, list, onSwitchAssistant]) return ( - - {assistants.length >= 10 && ( - - ⌘+K} - value={search} - onChange={(e) => setSearch(e.target.value)} - style={{ borderRadius: 16, borderWidth: 0.5 }} - onKeyDown={onSearch} - ref={searchRef} - onFocus={() => dispatch(setSearching(true))} - onBlur={() => { - dispatch(setSearching(false)) - setSearch('') - }} - allowClear - /> - - )} - setDragging(true)} - onDragEnd={() => setDragging(false)}> - {(assistant) => { - const isCurrent = assistant.id === activeAssistant?.id - return ( - - onSwitchAssistant(assistant)} className={isCurrent ? 'active' : ''}> - {assistant.name || t('chat.default.name')} - {isCurrent && ( - EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}> - - - )} - {false && {assistant.topics.length}} - - - ) - }} - - {!dragging && ( - - - - {t('chat.add.assistant.title')} - - - )} - + + + {assistants.length >= 10 && ( + + ⌘+K} + value={search} + onChange={(e) => setSearch(e.target.value)} + style={{ borderRadius: 16, borderWidth: 0.5 }} + onKeyDown={onSearch} + ref={searchRef} + onFocus={() => dispatch(setSearching(true))} + onBlur={() => { + dispatch(setSearching(false)) + setSearch('') + }} + allowClear + /> + + )} + setDragging(true)} + onDragEnd={() => setDragging(false)}> + {(assistant) => { + const isCurrent = assistant.id === activeAssistant?.id + return ( + + onSwitchAssistant(assistant)} className={isCurrent ? 'active' : ''}> + {assistant.name || t('chat.default.name')} + {isCurrent && ( + EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}> + + + )} + {false && {assistant.topics.length}} + + + ) + }} + + {!dragging && ( + + + + {t('chat.add.assistant.title')} + + + )} +
+
+
) } const Container = styled.div` display: flex; flex-direction: column; - height: calc(100vh - var(--navbar-height)); - overflow-y: auto; padding-top: 10px; - padding-bottom: 10px; ` const AssistantItem = styled.div` diff --git a/src/renderer/src/pages/home/Tabs/Settings.tsx b/src/renderer/src/pages/home/Tabs/Settings.tsx index 97415f97..909a500c 100644 --- a/src/renderer/src/pages/home/Tabs/Settings.tsx +++ b/src/renderer/src/pages/home/Tabs/Settings.tsx @@ -1,5 +1,6 @@ import { CheckOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons' import { HStack } from '@renderer/components/Layout' +import { Scrollbar } from '@renderer/components/Scrollbar' import { DEFAULT_CONEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant' import { useAssistant } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' @@ -97,183 +98,185 @@ const SettingsTab: FC = (props) => { }, [assistant]) return ( - - - {t('settings.messages.model.title')}{' '} - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - {t('model.stream_output')} - { - setStreamOutput(checked) - onUpdateAssistantSettings({ streamOutput: checked }) - }} - /> - - - - - - + + + + {t('settings.messages.model.title')}{' '} + + + + + + + + - - { - setEnableMaxTokens(enabled) - onUpdateAssistantSettings({ enableMaxTokens: enabled }) - }} - /> - - - - - - - {t('settings.messages.title')} - - - {t('settings.messages.divider')} - dispatch(setShowMessageDivider(checked))} - /> - - - - {t('settings.messages.use_serif_font')} - dispatch(setMessageFont(checked ? 'serif' : 'system'))} - /> - - - - {t('chat.settings.show_line_numbers')} - dispatch(setCodeShowLineNumbers(checked))} - /> - - - - {t('settings.font_size.title')} - - - - setFontSizeValue(value)} - onChangeComplete={(value) => dispatch(setFontSize(value))} - min={12} - max={22} - step={1} - marks={{ - 12: A, - 14: {t('common.default')}, - 22: A + + + + + + + + + + + + + + + + + + + {t('model.stream_output')} + { + setStreamOutput(checked) + onUpdateAssistantSettings({ streamOutput: checked }) }} /> - - - {t('settings.messages.input.title')} - - - {t('settings.messages.input.show_estimated_tokens')} - dispatch(setShowInputEstimatedTokens(checked))} + + + + + + + + + + { + setEnableMaxTokens(enabled) + onUpdateAssistantSettings({ enableMaxTokens: enabled }) + }} + /> + + + + + + + {t('settings.messages.title')} + + + {t('settings.messages.divider')} + dispatch(setShowMessageDivider(checked))} + /> + + + + {t('settings.messages.use_serif_font')} + dispatch(setMessageFont(checked ? 'serif' : 'system'))} + /> + + + + {t('chat.settings.show_line_numbers')} + dispatch(setCodeShowLineNumbers(checked))} + /> + + + + {t('settings.font_size.title')} + + + + setFontSizeValue(value)} + onChangeComplete={(value) => dispatch(setFontSize(value))} + min={12} + max={22} + step={1} + marks={{ + 12: A, + 14: {t('common.default')}, + 22: A + }} + /> + + + {t('settings.messages.input.title')} + + + {t('settings.messages.input.show_estimated_tokens')} + dispatch(setShowInputEstimatedTokens(checked))} + /> + + + + {t('settings.messages.input.paste_long_text_as_file')} + dispatch(setPasteLongTextAsFile(checked))} + /> + + + + {t('settings.messages.markdown_rendering_input_message')} + dispatch(setRenderInputMessageAsMarkdown(checked))} + /> + + + + {t('settings.messages.input.send_shortcuts')} + + } - options={[ - { value: 'Enter', label: `Enter ${t('chat.input.send')}` }, - { value: 'Shift+Enter', label: `Shift + Enter ${t('chat.input.send')}` } - ]} - onChange={(value) => setSendMessageShortcut(value)} - style={{ width: '100%', marginTop: 10 }} - /> - + + ) } diff --git a/src/renderer/src/pages/home/Tabs/Topics.tsx b/src/renderer/src/pages/home/Tabs/Topics.tsx index 7a16869c..e1e7570c 100644 --- a/src/renderer/src/pages/home/Tabs/Topics.tsx +++ b/src/renderer/src/pages/home/Tabs/Topics.tsx @@ -8,6 +8,7 @@ import { } from '@ant-design/icons' import DragableList from '@renderer/components/DragableList' import PromptPopup from '@renderer/components/Popups/PromptPopup' +import { Scrollbar } from '@renderer/components/Scrollbar' import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' import { TopicManager } from '@renderer/hooks/useTopic' @@ -177,38 +178,40 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic ) return ( - - - {(topic) => { - const isActive = topic.id === activeTopic?.id - return ( - - onSwitchTopic(topic)}> - {topic.name.replace('`', '')} - {showTopicTime && {dayjs(topic.createdAt).format('MM/DD HH:mm')}} - {isActive && ( - { - e.stopPropagation() - if (assistant.topics.length === 1) { - return onClearMessages() - } - onDeleteTopic(topic) - }}> - - - )} - - - ) - }} - -
-
+ + + + {(topic) => { + const isActive = topic.id === activeTopic?.id + return ( + + onSwitchTopic(topic)}> + {topic.name.replace('`', '')} + {showTopicTime && {dayjs(topic.createdAt).format('MM/DD HH:mm')}} + {isActive && ( + { + e.stopPropagation() + if (assistant.topics.length === 1) { + return onClearMessages() + } + onDeleteTopic(topic) + }}> + + + )} + + + ) + }} + +
+
+
) } @@ -216,7 +219,6 @@ const Container = styled.div` display: flex; flex-direction: column; padding-top: 10px; - max-height: calc(100vh - var(--navbar-height) - 70px); ` const TopicListItem = styled.div` diff --git a/src/renderer/src/pages/settings/ProviderSettings/index.tsx b/src/renderer/src/pages/settings/ProviderSettings/index.tsx index 39035f59..2a14de96 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/index.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/index.tsx @@ -1,5 +1,6 @@ import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons' import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd' +import { Scrollbar } from '@renderer/components/Scrollbar' import { getProviderLogo } from '@renderer/config/providers' import { useAllProviders, useProviders } from '@renderer/hooks/useProvider' import { Provider } from '@renderer/types' @@ -85,56 +86,58 @@ const ProvidersList: FC = () => { return ( - - setDragging(true)} onDragEnd={onDragEnd}> - - {(provided) => ( -
- {providers.map((provider, index) => ( - - {(provided) => ( -
- - setSelectedProvider(provider)}> - {provider.isSystem && ( - - )} - {!provider.isSystem && ( - - {getFirstCharacter(provider.name)} - - )} - - {provider.isSystem ? t(`provider.${provider.id}`) : provider.name} - - {provider.enabled && ( - - ON - - )} - - -
- )} -
- ))} -
- )} -
-
-
+ + + setDragging(true)} onDragEnd={onDragEnd}> + + {(provided) => ( +
+ {providers.map((provider, index) => ( + + {(provided) => ( +
+ + setSelectedProvider(provider)}> + {provider.isSystem && ( + + )} + {!provider.isSystem && ( + + {getFirstCharacter(provider.name)} + + )} + + {provider.isSystem ? t(`provider.${provider.id}`) : provider.name} + + {provider.enabled && ( + + ON + + )} + + +
+ )} +
+ ))} +
+ )} +
+
+
+
{!dragging && (