feat: new inputbar style
This commit is contained in:
parent
34d99b711c
commit
4fc53d7c19
@ -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
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -40,6 +40,7 @@
|
||||
|
||||
--navbar-background: rgba(0, 0, 0, 0.8);
|
||||
--sidebar-background: rgba(0, 0, 0, 0.8);
|
||||
--input-bar-background: rgba(255, 255, 255, 0.02);
|
||||
|
||||
--navbar-height: 42px;
|
||||
--sidebar-width: 55px;
|
||||
@ -47,7 +48,7 @@
|
||||
--topic-list-width: 260px;
|
||||
--settings-width: var(--assistants-width);
|
||||
--status-bar-height: 40px;
|
||||
--input-bar-height: 115px;
|
||||
--input-bar-height: 85px;
|
||||
}
|
||||
|
||||
body[theme-mode='light'] {
|
||||
@ -87,6 +88,7 @@ body[theme-mode='light'] {
|
||||
|
||||
--navbar-background: rgba(255, 255, 255, 0.8);
|
||||
--sidebar-background: rgba(255, 255, 255, 0.8);
|
||||
--input-bar-background: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
*,
|
||||
@ -155,3 +157,7 @@ body,
|
||||
position: relative;
|
||||
animation: flash 0.5s ease-out infinite alternate;
|
||||
}
|
||||
|
||||
.ant-segmented-group {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
user-select: text;
|
||||
word-break: break-word;
|
||||
|
||||
h1:first-child,
|
||||
h2:first-child,
|
||||
@ -72,6 +73,9 @@
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
&::marker {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
|
||||
li > 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;
|
||||
|
||||
24
src/renderer/src/components/Avatar/ModelAvatar.tsx
Normal file
24
src/renderer/src/components/Avatar/ModelAvatar.tsx
Normal file
@ -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<Props> = ({ model, size, props }) => {
|
||||
return (
|
||||
<Avatar
|
||||
src={getModelLogo(model?.id || '')}
|
||||
style={{ width: size, height: size, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
{...props}>
|
||||
{first(model?.name)}
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelAvatar
|
||||
@ -39,7 +39,6 @@ const TopViewContainer: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ children }) => {
|
||||
)
|
||||
}, [])
|
||||
|
||||
console.debug(
|
||||
'[TopView]',
|
||||
elements.map((el) => [el.id, el.element])
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
|
||||
@ -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': '请输入用户名',
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import {
|
||||
CheckOutlined,
|
||||
CopyOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
MenuOutlined,
|
||||
@ -47,6 +46,7 @@ const MessageItem: FC<Props> = ({ 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<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
}, [message])
|
||||
|
||||
return (
|
||||
<MessageContainer key={message.id} className="message" style={{ border: messageBorder }}>
|
||||
<MessageContainer key={message.id} className="message">
|
||||
<MessageHeader>
|
||||
<AvatarWrapper>
|
||||
{isAssistantMessage ? (
|
||||
<Avatar src={avatarSource} size={35}>
|
||||
<Avatar src={avatarSource} size={35} style={{ borderRadius: '20%' }}>
|
||||
{avatarName}
|
||||
</Avatar>
|
||||
) : (
|
||||
<Avatar src={avatar} size={35} />
|
||||
<Avatar src={avatar} size={35} style={{ borderRadius: '20%' }} />
|
||||
)}
|
||||
<UserWrap>
|
||||
<UserName>{username}</UserName>
|
||||
@ -137,55 +137,58 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
</MessageHeader>
|
||||
<MessageContent style={{ fontFamily }}>
|
||||
<MessageItem />
|
||||
{message.usage && !generating && (
|
||||
<MessageMetadata>
|
||||
Tokens: {message.usage.total_tokens} | ↑{message.usage.prompt_tokens}↓{message.usage.completion_tokens}
|
||||
</MessageMetadata>
|
||||
)}
|
||||
{showMenu && (
|
||||
<MenusBar className={`menubar ${isLastMessage && 'show'} ${(!isLastMessage || isUserMessage) && 'user'}`}>
|
||||
{message.role === 'user' && (
|
||||
<Tooltip title="Edit" mouseEnterDelay={0.8}>
|
||||
<ActionButton onClick={onEdit}>
|
||||
<EditOutlined />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
|
||||
<ActionButton onClick={onCopy}>
|
||||
{!copied && <CopyOutlined />}
|
||||
{copied && <CheckOutlined style={{ color: 'var(--color-primary)' }} />}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
{canRegenerate && (
|
||||
<SelectModelDropdown model={model} onSelect={onRegenerate} placement="topRight">
|
||||
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||
<ActionButton>
|
||||
<SyncOutlined />
|
||||
<MessageFooter style={{ border: messageBorder }}>
|
||||
{showMenu && (
|
||||
<MenusBar className={`menubar ${isLastMessage && 'show'} ${(!isLastMessage || isUserMessage) && 'user'}`}>
|
||||
{message.role === 'user' && (
|
||||
<Tooltip title="Edit" mouseEnterDelay={0.8}>
|
||||
<ActionButton onClick={onEdit}>
|
||||
<EditOutlined />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</SelectModelDropdown>
|
||||
)}
|
||||
<Popconfirm
|
||||
title={t('message.message.delete.content')}
|
||||
okButtonProps={{ danger: true }}
|
||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
||||
onConfirm={() => onDeleteMessage?.(message)}>
|
||||
<Tooltip title={t('common.delete')} mouseEnterDelay={1}>
|
||||
<ActionButton>
|
||||
<DeleteOutlined />
|
||||
)}
|
||||
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
|
||||
<ActionButton onClick={onCopy}>
|
||||
{!copied && <i className="iconfont icon-copy"></i>}
|
||||
{copied && <CheckOutlined style={{ color: 'var(--color-primary)' }} />}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
{!isUserMessage && (
|
||||
<Dropdown menu={{ items: dropdownItems }} trigger={['click']} placement="topRight" arrow>
|
||||
<ActionButton>
|
||||
<MenuOutlined />
|
||||
</ActionButton>
|
||||
</Dropdown>
|
||||
)}
|
||||
</MenusBar>
|
||||
)}
|
||||
{canRegenerate && (
|
||||
<SelectModelDropdown model={model} onSelect={onRegenerate} placement="topRight">
|
||||
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||
<ActionButton>
|
||||
<SyncOutlined />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</SelectModelDropdown>
|
||||
)}
|
||||
<Popconfirm
|
||||
title={t('message.message.delete.content')}
|
||||
okButtonProps={{ danger: true }}
|
||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
||||
onConfirm={() => onDeleteMessage?.(message)}>
|
||||
<Tooltip title={t('common.delete')} mouseEnterDelay={1}>
|
||||
<ActionButton>
|
||||
<DeleteOutlined />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
{!isUserMessage && (
|
||||
<Dropdown menu={{ items: dropdownItems }} trigger={['click']} placement="topRight" arrow>
|
||||
<ActionButton>
|
||||
<MenuOutlined />
|
||||
</ActionButton>
|
||||
</Dropdown>
|
||||
)}
|
||||
</MenusBar>
|
||||
)}
|
||||
{showMetadata && (
|
||||
<MessageMetadata>
|
||||
Tokens: {message?.usage?.total_tokens} | ↑{message?.usage?.prompt_tokens} | ↓
|
||||
{message?.usage?.completion_tokens}
|
||||
</MessageMetadata>
|
||||
)}
|
||||
</MessageFooter>
|
||||
</MessageContent>
|
||||
</MessageContainer>
|
||||
)
|
||||
@ -194,9 +197,8 @@ const MessageItem: FC<Props> = ({ 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);
|
||||
|
||||
@ -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<Props> = ({ assistant }) => {
|
||||
return (
|
||||
<SelectModelDropdown model={model} onSelect={setModel}>
|
||||
<DropdownButton size="small" type="default">
|
||||
<Avatar src={getModelLogo(model?.id || '')} style={{ width: 20, height: 20 }} />
|
||||
<ModelAvatar model={model} size={20} />
|
||||
<ModelName>{model ? upperFirst(model.name) : t('button.select_model')}</ModelName>
|
||||
</DropdownButton>
|
||||
</SelectModelDropdown>
|
||||
|
||||
@ -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<Props & PropsWithChildren> = ({ 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],
|
||||
|
||||
@ -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<Props> = ({ 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<Props> = ({ assistant, setActiveTopic }) => {
|
||||
}, [assistant])
|
||||
|
||||
return (
|
||||
<Container id="inputbar" style={{ minHeight: expended ? '80%' : 'var(--input-bar-height)' }}>
|
||||
<Container
|
||||
id="inputbar"
|
||||
style={{ minHeight: expended ? '60%' : 'var(--input-bar-height)' }}
|
||||
className={inputFocus ? 'focus' : ''}>
|
||||
<Toolbar>
|
||||
<ToolbarMenu>
|
||||
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
||||
@ -179,11 +183,20 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
</Tooltip>
|
||||
{showInputEstimatedTokens && (
|
||||
<TextCount>
|
||||
<Tooltip title={t('chat.input.context_count.tip')}>
|
||||
<Tag style={{ cursor: 'pointer' }}>{assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT}</Tag>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('chat.input.estimated_tokens.tip')}>
|
||||
<Tag style={{ cursor: 'pointer' }}>↑ {`${inputTokenCount} / ${estimateTokenCount}`}</Tag>
|
||||
<Tooltip title={t('chat.input.context_count.tip') + ' | ' + t('chat.input.estimated_tokens.tip')}>
|
||||
<Tag
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
borderRadius: '20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '2px 8px'
|
||||
}}>
|
||||
<i className="iconfont icon-history" style={{ marginRight: '3px' }} />
|
||||
{assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT}
|
||||
<Divider type="vertical" style={{ marginTop: 2, marginLeft: 5, marginRight: 5 }} />↑
|
||||
{`${inputTokenCount} / ${estimateTokenCount}`}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
</TextCount>
|
||||
)}
|
||||
@ -196,7 +209,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<SendMessageButton sendMessage={sendMessage} />
|
||||
<SendMessageButton sendMessage={sendMessage} disabled={generating || !text} />
|
||||
</ToolbarMenu>
|
||||
</Toolbar>
|
||||
<Textarea
|
||||
@ -209,6 +222,8 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
variant="borderless"
|
||||
ref={inputRef}
|
||||
styles={{ textarea: { paddingLeft: 0 } }}
|
||||
onFocus={() => setInputFocus(true)}
|
||||
onBlur={() => setInputFocus(false)}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
@ -217,10 +232,14 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
border-top: 0.5px solid var(--color-border);
|
||||
height: var(--input-bar-height);
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
margin: 0 20px 15px 20px;
|
||||
border-radius: 10px;
|
||||
&.focus {
|
||||
}
|
||||
`
|
||||
|
||||
const Textarea = styled(TextArea)`
|
||||
@ -229,13 +248,16 @@ const Textarea = styled(TextArea)`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin: 0 15px 5px 15px;
|
||||
font-family: Ubuntu;
|
||||
resize: vertical;
|
||||
overflow: auto;
|
||||
`
|
||||
|
||||
const Toolbar = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 3px 10px;
|
||||
padding: 0 10px;
|
||||
`
|
||||
|
||||
const ToolbarMenu = styled.div`
|
||||
|
||||
@ -1,44 +1,22 @@
|
||||
import { ArrowUpOutlined, EnterOutlined } from '@ant-design/icons'
|
||||
import { SendOutlined } from '@ant-design/icons'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { Dropdown, MenuProps } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Props {
|
||||
disabled: boolean
|
||||
sendMessage: () => void
|
||||
}
|
||||
|
||||
const SendMessageButton: FC<Props> = ({ sendMessage }) => {
|
||||
const { sendMessageShortcut, setSendMessageShortcut } = useSettings()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const sendSettingItems: MenuProps['items'] = [
|
||||
{
|
||||
label: `Enter ${t('chat.input.send')}`,
|
||||
key: 'Enter',
|
||||
icon: <EnterOutlined />,
|
||||
onClick: () => setSendMessageShortcut('Enter')
|
||||
},
|
||||
{
|
||||
label: `Shift+Enter ${t('chat.input.send')}`,
|
||||
key: 'Shift+Enter',
|
||||
icon: <ArrowUpOutlined />,
|
||||
onClick: () => setSendMessageShortcut('Shift+Enter')
|
||||
}
|
||||
]
|
||||
|
||||
const SendMessageButton: FC<Props> = ({ disabled, sendMessage }) => {
|
||||
return (
|
||||
<Dropdown.Button
|
||||
size="small"
|
||||
<i
|
||||
className="iconfont icon-ic_send"
|
||||
onClick={sendMessage}
|
||||
trigger={['click']}
|
||||
arrow
|
||||
menu={{ items: sendSettingItems, selectable: true, defaultSelectedKeys: [sendMessageShortcut] }}
|
||||
style={{ width: 'auto' }}>
|
||||
{t('chat.input.send')}
|
||||
<SendOutlined />
|
||||
</Dropdown.Button>
|
||||
style={{
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
color: disabled ? 'var(--color-text-3)' : 'var(--color-primary)',
|
||||
fontSize: 22,
|
||||
transition: 'all 0.2s'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { BarsOutlined, SettingOutlined } from '@ant-design/icons'
|
||||
import { useShowRightSidebar } from '@renderer/hooks/useStore'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Segmented } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@ -54,14 +56,16 @@ const RightSidebar: FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Tabs>
|
||||
<Tab className={tab === 'topic' ? 'active' : ''} onClick={() => setTab('topic')}>
|
||||
{t('common.topics')}
|
||||
</Tab>
|
||||
<Tab className={tab === 'settings' ? 'active' : ''} onClick={() => setTab('settings')}>
|
||||
{t('settings.title')}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Segmented
|
||||
value={tab}
|
||||
style={{ borderRadius: 0, padding: '10px', gap: 5, borderBottom: '0.5px solid var(--color-border)' }}
|
||||
options={[
|
||||
{ label: t('common.topics'), value: 'topic', icon: <BarsOutlined /> },
|
||||
{ label: t('settings.title'), value: 'settings', icon: <SettingOutlined /> }
|
||||
]}
|
||||
block
|
||||
onChange={(value) => setTab(value as 'topic' | 'settings')}
|
||||
/>
|
||||
<TabContent>
|
||||
{tab === 'topic' && <TopicsTab {...props} />}
|
||||
{tab === 'settings' && <SettingsTab assistant={props.assistant} />}
|
||||
@ -82,29 +86,6 @@ const Container = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const Tabs = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 0.5px solid var(--color-border);
|
||||
padding: 0 10px;
|
||||
`
|
||||
|
||||
const Tab = styled.div`
|
||||
padding: 8px 0;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-3);
|
||||
&.active {
|
||||
color: var(--color-text-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
`
|
||||
|
||||
const TabContent = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import { CheckOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
@ -7,7 +7,7 @@ import { SettingDivider, SettingRow, SettingRowTitle, SettingSubtitle } from '@r
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setMessageFont, setShowInputEstimatedTokens, setShowMessageDivider } from '@renderer/store/settings'
|
||||
import { Assistant, AssistantSettings } from '@renderer/types'
|
||||
import { Col, InputNumber, Row, Slider, Switch, Tooltip } from 'antd'
|
||||
import { Col, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { debounce } from 'lodash'
|
||||
import { FC, useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -27,7 +27,8 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const { showMessageDivider, messageFont, showInputEstimatedTokens } = useSettings()
|
||||
const { showMessageDivider, messageFont, showInputEstimatedTokens, sendMessageShortcut, setSendMessageShortcut } =
|
||||
useSettings()
|
||||
|
||||
const onUpdateAssistantSettings = useCallback(
|
||||
debounce(
|
||||
@ -104,27 +105,15 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={18}>
|
||||
<Col span={24}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1.2}
|
||||
onChange={onTemperatureChange}
|
||||
value={typeof temperature === 'number' ? temperature : 0}
|
||||
marks={{ 0: '0', 0.7: '0.7', 1.2: '1.2' }}
|
||||
step={0.1}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<InputNumberic
|
||||
min={0}
|
||||
max={1.2}
|
||||
step={0.1}
|
||||
value={temperature}
|
||||
onChange={onTemperatureChange}
|
||||
controls={false}
|
||||
size="small"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row align="middle">
|
||||
<Label>{t('chat.settings.conext_count')}</Label>
|
||||
@ -133,27 +122,15 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={18}>
|
||||
<Col span={24}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={20}
|
||||
marks={{ 0: '0', 10: '10', 20: t('chat.settings.max') }}
|
||||
onChange={onConextCountChange}
|
||||
value={typeof contextCount === 'number' ? contextCount : 0}
|
||||
step={1}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<InputNumberic
|
||||
min={0}
|
||||
max={20}
|
||||
step={1}
|
||||
value={contextCount}
|
||||
onChange={onConextCountChange}
|
||||
controls={false}
|
||||
size="small"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row align="middle" justify="space-between" style={{ marginBottom: 8 }}>
|
||||
<HStack alignItems="center">
|
||||
@ -173,7 +150,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
</Row>
|
||||
{enableMaxTokens && (
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={16}>
|
||||
<Col span={24}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={32000}
|
||||
@ -182,18 +159,6 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
step={100}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<InputNumberic
|
||||
min={0}
|
||||
max={32000}
|
||||
step={100}
|
||||
value={maxTokens}
|
||||
onChange={onMaxTokensChange}
|
||||
controls={true}
|
||||
style={{ width: '100%' }}
|
||||
size="small"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<SettingSubtitle>{t('settings.messages.title')}</SettingSubtitle>
|
||||
@ -227,6 +192,19 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.send_shortcuts')}</SettingRowTitleSmall>
|
||||
</SettingRow>
|
||||
<Select
|
||||
value={sendMessageShortcut}
|
||||
menuItemSelectedIcon={<CheckOutlined />}
|
||||
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 }}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@ -238,16 +216,6 @@ const Container = styled.div`
|
||||
padding: 0 15px;
|
||||
`
|
||||
|
||||
const InputNumberic = styled(InputNumber)`
|
||||
width: 45px;
|
||||
padding: 0;
|
||||
margin-left: 5px;
|
||||
text-align: center;
|
||||
.ant-input-number-input {
|
||||
text-align: center;
|
||||
}
|
||||
`
|
||||
|
||||
const Label = styled.p`
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
|
||||
@ -136,7 +136,7 @@ const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
padding: 15px 10px;
|
||||
padding: 10px 10px;
|
||||
`
|
||||
|
||||
const TopicListItem = styled.div`
|
||||
|
||||
@ -8,12 +8,20 @@ import { useTheme } from './ThemeProvider'
|
||||
const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const { language } = useSettings()
|
||||
const { theme: _theme } = useTheme()
|
||||
const isDarkTheme = _theme === 'dark'
|
||||
|
||||
return (
|
||||
<ConfigProvider
|
||||
locale={getAntdLocale(language)}
|
||||
theme={{
|
||||
algorithm: [_theme === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm],
|
||||
components: {
|
||||
Segmented: {
|
||||
trackBg: 'transparent',
|
||||
itemSelectedBg: isDarkTheme ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)',
|
||||
boxShadowTertiary: undefined
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: '#00b96b',
|
||||
borderRadius: 8
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user