From 4fc53d7c19dc98445236f16cb5e54e8e9d303aac Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Fri, 9 Aug 2024 18:56:45 +0800 Subject: [PATCH] feat: new inputbar style --- README.md | 8 +- .../src/assets/fonts/icon-fonts/iconfont.css | 14 +- .../src/assets/fonts/icon-fonts/iconfont.ttf | Bin 4372 -> 5120 bytes .../src/assets/fonts/icon-fonts/iconfont.woff | Bin 2708 -> 3072 bytes .../assets/fonts/icon-fonts/iconfont.woff2 | Bin 2204 -> 2548 bytes src/renderer/src/assets/styles/index.scss | 8 +- src/renderer/src/assets/styles/markdown.scss | 6 +- .../src/components/Avatar/ModelAvatar.tsx | 24 ++++ src/renderer/src/components/TopView/index.tsx | 10 -- src/renderer/src/i18n/index.ts | 2 + .../src/pages/home/components/Message.tsx | 129 ++++++++++-------- .../home/components/SelectModelButton.tsx | 6 +- .../home/components/SelectModelDropdown.tsx | 4 +- .../pages/home/components/input/Inputbar.tsx | 44 ++++-- .../components/input/SendMessageButton.tsx | 44 ++---- .../home/components/sidebar/RightSidebar.tsx | 43 ++---- .../home/components/sidebar/SettingsTab.tsx | 72 +++------- .../home/components/sidebar/TopicsTab.tsx | 2 +- src/renderer/src/providers/AntdProvider.tsx | 8 ++ 19 files changed, 218 insertions(+), 206 deletions(-) create mode 100644 src/renderer/src/components/Avatar/ModelAvatar.tsx diff --git a/README.md b/README.md index 0205a223..b6df4a57 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,12 @@ Cherry Studio is a desktop client that supports for multiple LLM providers, avai 6. Code highlighting. 7. Mermaid chart -# 👥 Community - -Join our Telegram group to discuss Cherry Studio's latest developments and features! [Telegram community](https://t.me/CherryStudioAI) - # 🖥️ Develop +## Recommended IDE Setup + +- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + ## Project Setup ### Install diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.css b/src/renderer/src/assets/fonts/icon-fonts/iconfont.css index 95abc881..a80dfc13 100644 --- a/src/renderer/src/assets/fonts/icon-fonts/iconfont.css +++ b/src/renderer/src/assets/fonts/icon-fonts/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4563475 */ - src: url('iconfont.woff2?t=1722242729348') format('woff2'), - url('iconfont.woff?t=1722242729348') format('woff'), - url('iconfont.ttf?t=1722242729348') format('truetype'); + src: url('iconfont.woff2?t=1723186111414') format('woff2'), + url('iconfont.woff?t=1723186111414') format('woff'), + url('iconfont.ttf?t=1723186111414') format('truetype'); } .iconfont { @@ -13,6 +13,14 @@ -moz-osx-font-smoothing: grayscale; } +.icon-copy:before { + content: "\e6ae"; +} + +.icon-ic_send:before { + content: "\e795"; +} + .icon-dark1:before { content: "\e72f"; } diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf b/src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf index 5ea01e3541ccddf30476e0a8fb03934c0ed1f56a..4341a838aa2af4a3d187e06dd99dad5eb18affcb 100644 GIT binary patch delta 1260 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 IPyhe`0HAm4&;S4c delta 2199 zcmV;I2x#~86PytkcTYw#00961000P_01E&B000pb000P2kr*C-HVTs*kR1Uw0we=0 z3m5UTui*t-s-qDylXTGg-U|Rq69*NuhxFP^SzrjK+QtU zg4R=Ui&QLy2?_px!qDz%==PL7F@-htuT%fq>*QRf8z)a}cN2QZZT$ZmsB3-|NnljPAqPOW}7!+p~Uy+_xszhkC=awxP&AcMZHlvhtMd6 zn9*%ACFrbWgTxNatc^u2rY%uV>6C{pDtqSB)6a(a`B@6B z3+wdbvLfLK^2&;0VgfGF8O5P?!O4=yQ2NJ5bsZdk83rPj)$%?#!dIbHh3i^Hys;S~ zTl5Hz5rAtXUK++@b^NF>l_#%ybzOv*XDOyBdA+(~S;R18U1vUaaabH%f@+!Ll(Jd` z$hV&ek&05@3R8=6DKB0E$R$$g%)a}Qme{2T3>0`7yjjkd=cRGQBMOjcCRYtIh=2W} znhMN+i~Qu!2naQ%L-pfEuS-!bBH#D(CZSTNMH4wi2=~1&~B7vUnB@YlBEPDN0S zo*o?f<*m0jl8W6T4EQ#IJB+#~%+;?ja;`A2E8)od6T@!>{#%7Vcwk7xcy3OsOq? z_ccQ^=uj=GB}^47fpwcohh%HWPGSv9Vp14~mgZft%xQJ(%54Rb=JUxQb0=Kq{7mzX zU+2ge$RWj6!^N;{4HWI_yMmIhKc-Vkgo&jB3D?IGSl&loMQO$YVI~fv)JMMb(=6+M zi@S*9Xu?m$N z%crwtHM!(^g2{MiDK(S+u#n2zQ(4LED5uJ|Rp&743ds3)Tu1Hoe#n|@ z9$jq6G~HPQYuj59YHhEDMV<7u#T;9I#Z=k&c-d6NZ6cvWmA$=rh%Ehod-qf~fqVOUtHU-RbK2c)ZD>uKTi{J4Za^sdH8D_7&_7KCZfb_d zeA1iOe{rU_Zx6`LrLku)0&SS8&izE4Mf|o>tKD97{B?y=jwx8BS*DZ)uz2x*Js6W! z%2aas)@AA7N|{sy_u>0<{4!kt^PPyLuqmh;5@8W z1fhCU&WqP@0Fyp>bKQr7dpFy#qL!_T@!wC^?RhXWr!wwc?SSo(InaGgxY#zqv;GB6bklFmE39Z zUL+DqRDI#3-JqeiHzrkoLG`7mR`%8O#lvB1wmhY`9^?M^5Qo2fFDaTDod>Me znl38KX?gMU3ZjDPZv5|d8vn8GzzjC`+Gl6efvqRuJ*Zyb$NI;a z81_?&EzTSm_%T(r6zr-*8j6MpVrY&?++12Nf!4Eo`gdCE=F?XC`AhoY*e1<0GxUZkdWjjB;ncQ4&2q*<@ zQ`UfmvPJI^(*r(~gL+4+_S0i>IVlhu0S%U+7qWby9O7Cq zl_137oanIO9gEvRtJQ%!MTZ$05WyqltSKzy+72NM4jX7Cf> ul, @@ -103,7 +107,7 @@ pre { white-space: pre-wrap !important; - padding: 1em; + padding: 1em 0; border-radius: 5px; overflow-x: auto; font-family: 'Fira Code', 'Courier New', Courier, monospace; diff --git a/src/renderer/src/components/Avatar/ModelAvatar.tsx b/src/renderer/src/components/Avatar/ModelAvatar.tsx new file mode 100644 index 00000000..88165d02 --- /dev/null +++ b/src/renderer/src/components/Avatar/ModelAvatar.tsx @@ -0,0 +1,24 @@ +import { getModelLogo } from '@renderer/config/provider' +import { Model } from '@renderer/types' +import { Avatar, AvatarProps } from 'antd' +import { first } from 'lodash' +import { FC } from 'react' + +interface Props { + model: Model + size: number + props?: AvatarProps +} + +const ModelAvatar: FC = ({ model, size, props }) => { + return ( + + {first(model?.name)} + + ) +} + +export default ModelAvatar diff --git a/src/renderer/src/components/TopView/index.tsx b/src/renderer/src/components/TopView/index.tsx index 6d2755e1..868a1c39 100644 --- a/src/renderer/src/components/TopView/index.tsx +++ b/src/renderer/src/components/TopView/index.tsx @@ -39,7 +39,6 @@ const TopViewContainer: React.FC = ({ children }) => { }, [messageApi, modal]) onPop = () => { - console.debug('[TopView] onPop') const views = [...elementsRef.current] views.pop() elementsRef.current = views @@ -47,8 +46,6 @@ const TopViewContainer: React.FC = ({ children }) => { } onShow = ({ element, id }: ElementItem) => { - console.debug('[TopView] onShow', id) - if (!elementsRef.current.find((el) => el.id === id)) { elementsRef.current = elementsRef.current.concat([{ element, id }]) setElements(elementsRef.current) @@ -56,13 +53,11 @@ const TopViewContainer: React.FC = ({ children }) => { } onHide = (id: string) => { - console.debug('[TopView] onHide', id, elementsRef.current) elementsRef.current = elementsRef.current.filter((el) => el.id !== id) setElements(elementsRef.current) } onHideAll = () => { - console.debug('[TopView] onHideAll') setElements([]) elementsRef.current = [] } @@ -76,11 +71,6 @@ const TopViewContainer: React.FC = ({ children }) => { ) }, []) - console.debug( - '[TopView]', - elements.map((el) => [el.id, el.element]) - ) - return ( <> {children} diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts index 95cf273c..2f13444c 100644 --- a/src/renderer/src/i18n/index.ts +++ b/src/renderer/src/i18n/index.ts @@ -131,6 +131,7 @@ const resources = { 'messages.use_serif_font': 'Use serif font', 'messages.input.title': 'Input Settings', 'messages.input.show_estimated_tokens': 'Show estimated input tokens', + 'messages.input.send_shortcuts': 'Send shortcuts', 'general.title': 'General Settings', 'general.user_name': 'User Name', 'general.user_name.placeholder': 'Enter your name', @@ -345,6 +346,7 @@ const resources = { 'messages.use_serif_font': '使用衬线字体', 'messages.input.title': '输入设置', 'messages.input.show_estimated_tokens': '状态显示', + 'messages.input.send_shortcuts': '发送快捷键', 'general.title': '常规设置', 'general.user_name': '用户名', 'general.user_name.placeholder': '请输入用户名', diff --git a/src/renderer/src/pages/home/components/Message.tsx b/src/renderer/src/pages/home/components/Message.tsx index 4b57f568..65b636d4 100644 --- a/src/renderer/src/pages/home/components/Message.tsx +++ b/src/renderer/src/pages/home/components/Message.tsx @@ -1,6 +1,5 @@ import { CheckOutlined, - CopyOutlined, DeleteOutlined, EditOutlined, MenuOutlined, @@ -47,6 +46,7 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = const isUserMessage = message.role === 'user' const isAssistantMessage = message.role === 'assistant' const canRegenerate = isLastMessage && isAssistantMessage + const showMetadata = Boolean(message.usage) && !generating const onCopy = useCallback(() => { navigator.clipboard.writeText(message.content) @@ -119,15 +119,15 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = }, [message]) return ( - + {isAssistantMessage ? ( - + {avatarName} ) : ( - + )} {username} @@ -137,55 +137,58 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = - {message.usage && !generating && ( - - Tokens: {message.usage.total_tokens} | ↑{message.usage.prompt_tokens}↓{message.usage.completion_tokens} - - )} - {showMenu && ( - - {message.role === 'user' && ( - - - - - - )} - - - {!copied && } - {copied && } - - - {canRegenerate && ( - - - - + + {showMenu && ( + + {message.role === 'user' && ( + + + - - )} - } - onConfirm={() => onDeleteMessage?.(message)}> - - - + )} + + + {!copied && } + {copied && } - - {!isUserMessage && ( - - - - - - )} - - )} + {canRegenerate && ( + + + + + + + + )} + } + onConfirm={() => onDeleteMessage?.(message)}> + + + + + + + {!isUserMessage && ( + + + + + + )} + + )} + {showMetadata && ( + + Tokens: {message?.usage?.total_tokens} | ↑{message?.usage?.prompt_tokens} | ↓ + {message?.usage?.completion_tokens} + + )} + ) @@ -194,9 +197,8 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = const MessageContainer = styled.div` display: flex; flex-direction: column; - padding: 10px 20px; + padding: 0 20px; position: relative; - border-bottom: 0.5px dotted var(--color-border); .menubar { opacity: 0; transition: opacity 0.2s ease; @@ -205,7 +207,7 @@ const MessageContainer = styled.div` } &.user { position: absolute; - top: 15px; + top: 10px; right: 10px; } } @@ -257,6 +259,16 @@ const MessageContent = styled.div` margin-top: 5px; ` +const MessageFooter = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 2px 0; + margin: 2px 0 8px 0; + border-top: 0.5px dashed var(--color-border); +` + const MessageContentLoading = styled.div` display: flex; flex-direction: row; @@ -270,17 +282,18 @@ const MenusBar = styled.div` justify-content: flex-end; align-items: center; gap: 6px; + margin-left: -5px; ` const MessageMetadata = styled.div` font-size: 12px; color: var(--color-text-2); user-select: text; + margin: 2px 0; ` const ActionButton = styled.div` cursor: pointer; - border: 1px solid var(--color-border); border-radius: 8px; display: flex; flex-direction: row; @@ -288,7 +301,15 @@ const ActionButton = styled.div` align-items: center; width: 30px; height: 30px; - .anticon { + transition: all 0.3s ease; + &:hover { + background-color: var(--color-background-mute); + .anticon { + color: var(--color-text-1); + } + } + .anticon, + .iconfont { cursor: pointer; font-size: 14px; color: var(--color-icon); diff --git a/src/renderer/src/pages/home/components/SelectModelButton.tsx b/src/renderer/src/pages/home/components/SelectModelButton.tsx index ba228067..ce7765cc 100644 --- a/src/renderer/src/pages/home/components/SelectModelButton.tsx +++ b/src/renderer/src/pages/home/components/SelectModelButton.tsx @@ -1,7 +1,7 @@ -import { getModelLogo } from '@renderer/config/provider' +import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import { useAssistant } from '@renderer/hooks/useAssistant' import { Assistant } from '@renderer/types' -import { Avatar, Button } from 'antd' +import { Button } from 'antd' import { upperFirst } from 'lodash' import { FC } from 'react' import { useTranslation } from 'react-i18next' @@ -20,7 +20,7 @@ const SelectModelButton: FC = ({ assistant }) => { return ( - + {model ? upperFirst(model.name) : t('button.select_model')} diff --git a/src/renderer/src/pages/home/components/SelectModelDropdown.tsx b/src/renderer/src/pages/home/components/SelectModelDropdown.tsx index 97422c1c..f8de1629 100644 --- a/src/renderer/src/pages/home/components/SelectModelDropdown.tsx +++ b/src/renderer/src/pages/home/components/SelectModelDropdown.tsx @@ -2,7 +2,7 @@ import { getModelLogo } from '@renderer/config/provider' import { useProviders } from '@renderer/hooks/useProvider' import { Model } from '@renderer/types' import { Avatar, Dropdown, DropdownProps, MenuProps } from 'antd' -import { first, upperFirst } from 'lodash' +import { first, sortBy, upperFirst } from 'lodash' import { FC, PropsWithChildren } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -22,7 +22,7 @@ const SelectModelDropdown: FC = ({ children, model, o key: p.id, label: p.isSystem ? t(`provider.${p.id}`) : p.name, type: 'group', - children: p.models.map((m) => ({ + children: sortBy(p.models, 'name').map((m) => ({ key: m?.id, label: upperFirst(m?.name), defaultSelectedKeys: [model?.id], diff --git a/src/renderer/src/pages/home/components/input/Inputbar.tsx b/src/renderer/src/pages/home/components/input/Inputbar.tsx index 2541b0ea..986aaa51 100644 --- a/src/renderer/src/pages/home/components/input/Inputbar.tsx +++ b/src/renderer/src/pages/home/components/input/Inputbar.tsx @@ -18,7 +18,7 @@ import store, { useAppSelector } from '@renderer/store' import { setGenerating } from '@renderer/store/runtime' import { Assistant, Message, Topic } from '@renderer/types' import { uuid } from '@renderer/utils' -import { Button, Popconfirm, Tag, Tooltip } from 'antd' +import { Button, Divider, Popconfirm, Tag, Tooltip } from 'antd' import TextArea, { TextAreaRef } from 'antd/es/input/TextArea' import dayjs from 'dayjs' import { debounce, isEmpty } from 'lodash' @@ -37,6 +37,7 @@ let _text = '' const Inputbar: FC = ({ assistant, setActiveTopic }) => { const [text, setText] = useState(_text) + const [inputFocus, setInputFocus] = useState(false) const { addTopic } = useAssistant(assistant.id) const { sendMessageShortcut, showInputEstimatedTokens } = useSettings() const [expended, setExpend] = useState(false) @@ -141,7 +142,10 @@ const Inputbar: FC = ({ assistant, setActiveTopic }) => { }, [assistant]) return ( - + @@ -179,11 +183,20 @@ const Inputbar: FC = ({ assistant, setActiveTopic }) => { {showInputEstimatedTokens && ( - - {assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT} - - - ↑ {`${inputTokenCount} / ${estimateTokenCount}`} + + + + {assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT} + ↑ + {`${inputTokenCount} / ${estimateTokenCount}`} + )} @@ -196,7 +209,7 @@ const Inputbar: FC = ({ assistant, setActiveTopic }) => { )} - +