Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
885c578582 | ||
|
|
e61e4b109a | ||
|
|
f3bafbeb52 | ||
|
|
e55c0cdcef | ||
|
|
e73bbf4d6a | ||
|
|
3859289218 | ||
|
|
591bb45a4e | ||
|
|
b31f518fca | ||
|
|
dfbdb989db |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "0.6.7",
|
||||
"version": "0.6.8",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'iconfont'; /* Project id 4563475 */
|
||||
src: url('iconfont.woff2?t=1725450669049') format('woff2');
|
||||
src: url('iconfont.woff2?t=1725606177995') format('woff2');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -11,26 +11,26 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-a-darkmode:before {
|
||||
content: '\e6cd';
|
||||
}
|
||||
|
||||
.icon-ai-model:before {
|
||||
content: '\e827';
|
||||
}
|
||||
|
||||
.icon-ai-model1:before {
|
||||
content: '\ec09';
|
||||
}
|
||||
|
||||
.icon-gridlines:before {
|
||||
content: '\e942';
|
||||
}
|
||||
|
||||
.icon-grid-frame:before {
|
||||
content: '\e680';
|
||||
}
|
||||
|
||||
.icon-grid-row-2copy:before {
|
||||
content: '\e681';
|
||||
}
|
||||
|
||||
.icon-sidebar-left:before {
|
||||
content: '\e6ab';
|
||||
}
|
||||
|
||||
.icon-sidebar-right:before {
|
||||
content: '\e6ac';
|
||||
}
|
||||
|
||||
.icon-inbox:before {
|
||||
content: '\e869';
|
||||
}
|
||||
@@ -63,6 +63,14 @@
|
||||
content: '\e758';
|
||||
}
|
||||
|
||||
.icon-hide-sidebar:before {
|
||||
content: '\e8eb';
|
||||
}
|
||||
|
||||
.icon-show-sidebar:before {
|
||||
content: '\e944';
|
||||
}
|
||||
|
||||
.icon-a-addchat:before {
|
||||
content: '\e658';
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -72,6 +72,9 @@
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
pre {
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
&::marker {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
@@ -106,7 +109,6 @@
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap !important;
|
||||
padding: 1em 0;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
font-family: 'Fira Code', 'Courier New', Courier, monospace;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const DEFAULT_TEMPERATURE = 0.7
|
||||
export const DEFAULT_CONEXTCOUNT = 5
|
||||
export const DEFAULT_CONEXTCOUNT = 6
|
||||
export const DEFAULT_MAX_TOKENS = 4096
|
||||
export const FONT_FAMILY =
|
||||
"Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
|
||||
|
||||
@@ -71,11 +71,12 @@ const resources = {
|
||||
'topics.list': 'Topic List',
|
||||
'input.new_topic': 'New Topic',
|
||||
'input.topics': ' Topics ',
|
||||
'input.clear': 'Clear',
|
||||
'input.clear': 'Clear Messages',
|
||||
'input.new.context': 'Clear Context',
|
||||
'input.expand': 'Expand',
|
||||
'input.collapse': 'Collapse',
|
||||
'input.clear.title': 'Clear all messages?',
|
||||
'input.clear.content': 'Are you sure to clear all messages?',
|
||||
'input.clear.content': 'Do you want to clear all messages of the current topic?',
|
||||
'input.placeholder': 'Type your message here...',
|
||||
'input.send': 'Send',
|
||||
'input.pause': 'Pause',
|
||||
@@ -95,7 +96,8 @@ const resources = {
|
||||
'settings.set_as_default': 'Apply to default assistant',
|
||||
'settings.max': 'Max',
|
||||
'suggestions.title': 'Suggested Questions',
|
||||
'add.assistant.title': 'Add Assistant'
|
||||
'add.assistant.title': 'Add Assistant',
|
||||
'message.new.context': 'New Context'
|
||||
},
|
||||
agents: {
|
||||
title: 'Agents',
|
||||
@@ -320,11 +322,12 @@ const resources = {
|
||||
'topics.list': '话题列表',
|
||||
'input.new_topic': '新话题',
|
||||
'input.topics': ' 话题 ',
|
||||
'input.clear': '清除',
|
||||
'input.clear': '清除会话消息',
|
||||
'input.new.context': '清除上下文',
|
||||
'input.expand': '展开',
|
||||
'input.collapse': '收起',
|
||||
'input.clear.title': '清除所有消息?',
|
||||
'input.clear.content': '确定要清除所有消息吗?',
|
||||
'input.clear.title': '清除消息?',
|
||||
'input.clear.content': '确定要清除当前会话所有消息吗?',
|
||||
'input.placeholder': '在这里输入消息...',
|
||||
'input.send': '发送',
|
||||
'input.pause': '暂停',
|
||||
@@ -345,7 +348,8 @@ const resources = {
|
||||
'settings.set_as_default': '应用到默认助手',
|
||||
'settings.max': '不限',
|
||||
'suggestions.title': '建议的问题',
|
||||
'add.assistant.title': '添加智能体'
|
||||
'add.assistant.title': '添加智能体',
|
||||
'message.new.context': '清除上下文'
|
||||
},
|
||||
agents: {
|
||||
title: '智能体',
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
PauseCircleOutlined,
|
||||
QuestionCircleOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
|
||||
import { isWindows } from '@renderer/config/constant'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShowTopics } from '@renderer/hooks/useStore'
|
||||
@@ -44,6 +44,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const { sendMessageShortcut, showInputEstimatedTokens, fontSize } = useSettings()
|
||||
const [expended, setExpend] = useState(false)
|
||||
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
||||
const [contextCount, setContextCount] = useState(0)
|
||||
const generating = useAppSelector((state) => state.runtime.generating)
|
||||
const textareaRef = useRef<TextAreaRef>(null)
|
||||
const [files, setFiles] = useState<File[]>([])
|
||||
@@ -89,13 +90,15 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const inputTokenCount = useMemo(() => estimateInputTokenCount(text), [text])
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
const isEnterPressed = event.keyCode == 13
|
||||
|
||||
if (expended) {
|
||||
if (event.key === 'Escape') {
|
||||
return setExpend(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (sendMessageShortcut === 'Enter' && event.key === 'Enter') {
|
||||
if (sendMessageShortcut === 'Enter' && isEnterPressed) {
|
||||
if (event.shiftKey) {
|
||||
return
|
||||
}
|
||||
@@ -103,7 +106,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
return event.preventDefault()
|
||||
}
|
||||
|
||||
if (sendMessageShortcut === 'Shift+Enter' && event.key === 'Enter' && event.shiftKey) {
|
||||
if (sendMessageShortcut === 'Shift+Enter' && isEnterPressed && event.shiftKey) {
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
}
|
||||
@@ -128,6 +131,14 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
store.dispatch(setGenerating(false))
|
||||
}
|
||||
|
||||
const onNewContext = () => {
|
||||
if (generating) {
|
||||
onPause()
|
||||
return
|
||||
}
|
||||
EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT)
|
||||
}
|
||||
|
||||
const resizeTextArea = () => {
|
||||
const textArea = textareaRef.current?.resizableTextArea?.textArea
|
||||
if (textArea) {
|
||||
@@ -176,7 +187,10 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
setText(message.content)
|
||||
textareaRef.current?.focus()
|
||||
}),
|
||||
EventEmitter.on(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, _setEstimateTokenCount)
|
||||
EventEmitter.on(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, ({ tokensCount, contextCount }) => {
|
||||
_setEstimateTokenCount(tokensCount)
|
||||
setContextCount(contextCount)
|
||||
})
|
||||
]
|
||||
return () => unsubscribes.forEach((unsub) => unsub())
|
||||
}, [])
|
||||
@@ -210,6 +224,11 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
<FormOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={t('chat.input.new.context')} arrow>
|
||||
<ToolbarButton type="text" onClick={onNewContext}>
|
||||
<i className="iconfont icon-grid-row-2copy" />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
||||
<Popconfirm
|
||||
title={t('chat.input.clear.content')}
|
||||
@@ -245,9 +264,10 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
<TextCount>
|
||||
<Tooltip title={t('chat.input.context_count.tip') + ' | ' + t('chat.input.estimated_tokens.tip')}>
|
||||
<StyledTag>
|
||||
{assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT}
|
||||
<span style={isWindows ? { fontFamily: 'serif', marginRight: 2 } : { marginRight: 3 }}>⊙</span>
|
||||
{contextCount}
|
||||
<Divider type="vertical" style={{ marginTop: 2, marginLeft: 5, marginRight: 5 }} />↑{inputTokenCount}
|
||||
<span style={{ margin: '0 2px' }}>/</span>
|
||||
<span style={{ margin: '0 2px', fontSize: 10 }}>/</span>
|
||||
{estimateTokenCount}
|
||||
</StyledTag>
|
||||
</Tooltip>
|
||||
@@ -326,14 +346,19 @@ const ToolbarButton = styled(Button)`
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
&.anticon {
|
||||
.iconfont {
|
||||
font-size: 17px;
|
||||
}
|
||||
&.anticon,
|
||||
&.iconfont {
|
||||
transition: all 0.3s ease;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: var(--color-background-soft);
|
||||
.anticon {
|
||||
.anticon,
|
||||
.iconfont {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
@@ -356,6 +381,9 @@ const StyledTag = styled(Tag)`
|
||||
padding: 2px 8px;
|
||||
border-width: 0.5;
|
||||
margin: 0;
|
||||
height: 25px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
`
|
||||
|
||||
export default Inputbar
|
||||
|
||||
@@ -37,7 +37,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className, ...rest }) =
|
||||
}
|
||||
|
||||
return match ? (
|
||||
<div>
|
||||
<>
|
||||
<CodeHeader>
|
||||
<CodeLanguage>{'<' + match[1].toUpperCase() + '>'}</CodeLanguage>
|
||||
{!copied && <CopyIcon className="copy" onClick={onCopy} />}
|
||||
@@ -48,14 +48,24 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className, ...rest }) =
|
||||
language={match[1]}
|
||||
style={theme === ThemeMode.dark ? atomDark : oneLight}
|
||||
wrapLongLines={true}
|
||||
customStyle={{ borderTopLeftRadius: 0, borderTopRightRadius: 0, marginTop: 0 }}>
|
||||
customStyle={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
marginTop: 0,
|
||||
border: '0.5px solid var(--color-code-background)',
|
||||
borderTop: 'none'
|
||||
}}>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<code {...rest} className={className}>
|
||||
{children}
|
||||
</code>
|
||||
<SyntaxHighlighter
|
||||
{...rest}
|
||||
style={theme === ThemeMode.dark ? atomDark : oneLight}
|
||||
wrapLongLines={true}
|
||||
customStyle={{ border: '0.5px solid var(--color-code-background)', padding: '8px 12px' }}>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useRuntime } from '@renderer/hooks/useStore'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { Message, Model } from '@renderer/types'
|
||||
import { firstLetter, removeLeadingEmoji } from '@renderer/utils'
|
||||
import { Alert, Avatar, Dropdown, Popconfirm, Tooltip } from 'antd'
|
||||
import { Alert, Avatar, Divider, Dropdown, Popconfirm, Tooltip } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { upperFirst } from 'lodash'
|
||||
import { FC, memo, useCallback, useMemo, useState } from 'react'
|
||||
@@ -130,6 +130,14 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
|
||||
const showMiniApp = () => model?.provider && startMinAppById(model?.provider)
|
||||
|
||||
if (message.type === 'clear') {
|
||||
return (
|
||||
<Divider dashed style={{ padding: '0 20px' }} plain>
|
||||
{t('chat.message.new.context')}
|
||||
</Divider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageContainer key={message.id} className="message">
|
||||
<MessageHeader>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
||||
import { getTopic } from '@renderer/hooks/useTopic'
|
||||
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { estimateHistoryTokenCount, filterMessages } from '@renderer/services/messages'
|
||||
import { estimateHistoryTokenCount, filterMessages, getContextCount } from '@renderer/services/messages'
|
||||
import LocalStorage from '@renderer/services/storage'
|
||||
import { Assistant, Message, Model, Topic } from '@renderer/types'
|
||||
import { getBriefInfo, runAsyncFunction, uuid } from '@renderer/utils'
|
||||
@@ -89,6 +89,28 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
setMessages([])
|
||||
updateTopic({ ...topic, messages: [] })
|
||||
LocalStorage.clearTopicMessages(topic.id)
|
||||
}),
|
||||
EventEmitter.on(EVENT_NAMES.NEW_CONTEXT, () => {
|
||||
const lastMessage = last(messages)
|
||||
|
||||
if (lastMessage && lastMessage.type === 'clear') {
|
||||
return
|
||||
}
|
||||
|
||||
if (messages.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
onSendMessage({
|
||||
id: uuid(),
|
||||
assistantId: assistant.id,
|
||||
role: 'user',
|
||||
content: '',
|
||||
topicId: topic.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
status: 'success',
|
||||
type: 'clear'
|
||||
} as Message)
|
||||
})
|
||||
]
|
||||
return () => unsubscribes.forEach((unsub) => unsub())
|
||||
@@ -106,7 +128,10 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
}, [messages])
|
||||
|
||||
useEffect(() => {
|
||||
EventEmitter.emit(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, estimateHistoryTokenCount(assistant, messages))
|
||||
EventEmitter.emit(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, {
|
||||
tokensCount: estimateHistoryTokenCount(assistant, messages),
|
||||
contextCount: getContextCount(assistant, messages)
|
||||
})
|
||||
}, [assistant, messages])
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Switch } from 'antd'
|
||||
import { FC, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@@ -48,9 +47,9 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
|
||||
{showAssistants && (
|
||||
<NavbarLeft style={{ justifyContent: 'space-between', borderRight: 'none', padding: '0 8px' }}>
|
||||
<NewButton onClick={toggleShowAssistants} style={{ marginLeft: isMac ? 8 : 0 }}>
|
||||
<i className="iconfont icon-sidebar-right" />
|
||||
<i className="iconfont icon-hide-sidebar" />
|
||||
</NewButton>
|
||||
<NewButton onClick={onCreateAssistant} style={{ marginRight: 6 }}>
|
||||
<NewButton onClick={onCreateAssistant}>
|
||||
<i className="iconfont icon-a-addchat" />
|
||||
</NewButton>
|
||||
</NavbarLeft>
|
||||
@@ -66,7 +65,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
|
||||
<HStack alignItems="center">
|
||||
{!showAssistants && (
|
||||
<NewButton onClick={toggleShowAssistants} style={{ marginRight: isMac ? 8 : 25 }}>
|
||||
<i className="iconfont icon-sidebar-left" />
|
||||
<i className="iconfont icon-show-sidebar" />
|
||||
</NewButton>
|
||||
)}
|
||||
{showAssistants && (
|
||||
@@ -80,13 +79,13 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
|
||||
</NewButton>
|
||||
</NavbarCenter>
|
||||
)}
|
||||
<NavbarRight style={{ justifyContent: 'space-between', paddingRight: isWindows ? 130 : 12, flex: 1 }}>
|
||||
<NavbarRight style={{ justifyContent: 'space-between', paddingRight: isWindows ? 140 : 12, flex: 1 }}>
|
||||
<HStack alignItems="center">
|
||||
{!showAssistants && (topicPosition === 'left' ? !showTopics : true) && (
|
||||
<NewButton
|
||||
onClick={() => toggleShowAssistants()}
|
||||
style={{ marginRight: isMac ? 8 : 25, marginLeft: isMac ? 8 : 0 }}>
|
||||
<i className="iconfont icon-sidebar-left" />
|
||||
style={{ marginRight: isMac ? 8 : 25, marginLeft: isMac ? 4 : 0 }}>
|
||||
<i className="iconfont icon-show-sidebar" />
|
||||
</NewButton>
|
||||
)}
|
||||
<TitleText
|
||||
@@ -98,15 +97,16 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
|
||||
<SelectModelButton assistant={assistant} />
|
||||
</HStack>
|
||||
<HStack alignItems="center">
|
||||
<ThemeSwitch
|
||||
checkedChildren={<i className="iconfont icon-theme icon-dark1" />}
|
||||
unCheckedChildren={<i className="iconfont icon-theme icon-theme-light" />}
|
||||
checked={theme === 'dark'}
|
||||
onChange={toggleTheme}
|
||||
/>
|
||||
<NewButton onClick={toggleTheme} style={{ marginRight: 3 }}>
|
||||
{theme === 'dark' ? (
|
||||
<i className="iconfont icon-theme icon-theme-light" />
|
||||
) : (
|
||||
<i className="iconfont icon-a-darkmode" />
|
||||
)}
|
||||
</NewButton>
|
||||
{topicPosition === 'right' && (
|
||||
<NewButton onClick={toggleShowTopics}>
|
||||
<i className={`iconfont icon-sidebar-${showTopics ? 'left' : 'right'}`} />
|
||||
<i className={`iconfont icon-${showTopics ? 'show' : 'hide'}-sidebar`} />
|
||||
</NewButton>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -119,7 +119,7 @@ export const NewButton = styled.div`
|
||||
-webkit-app-region: none;
|
||||
border-radius: 4px;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
padding: 0 7px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
@@ -127,11 +127,14 @@ export const NewButton = styled.div`
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
.iconfont {
|
||||
font-size: 18px;
|
||||
font-size: 19px;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
.icon-a-addchat {
|
||||
font-size: 20px;
|
||||
&.icon-a-addchat {
|
||||
font-size: 20px;
|
||||
}
|
||||
&.icon-a-darkmode {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
.anticon {
|
||||
color: var(--color-icon);
|
||||
@@ -150,12 +153,4 @@ const TitleText = styled.span`
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
const ThemeSwitch = styled(Switch)`
|
||||
-webkit-app-region: no-drag;
|
||||
margin-right: 10px;
|
||||
.icon-theme {
|
||||
font-size: 14px;
|
||||
}
|
||||
`
|
||||
|
||||
export default HeaderNavbar
|
||||
|
||||
@@ -96,10 +96,9 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
<DragableList list={assistant.topics} onUpdate={updateTopics}>
|
||||
{(topic) => {
|
||||
const isActive = topic.id === activeTopic?.id
|
||||
const activeClass = topicPosition === 'left' ? 'active-left' : 'active-right'
|
||||
return (
|
||||
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
|
||||
<TopicListItem className={isActive ? activeClass : ''} onClick={() => onSwitchTopic(topic)}>
|
||||
<TopicListItem className={isActive ? 'active' : ''} onClick={() => onSwitchTopic(topic)}>
|
||||
{topic.name}
|
||||
</TopicListItem>
|
||||
</Dropdown>
|
||||
@@ -117,8 +116,6 @@ const Container = styled.div`
|
||||
padding-top: 10px;
|
||||
min-width: var(--topic-list-width);
|
||||
max-width: var(--topic-list-width);
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
border-left: 0.5px solid var(--color-border);
|
||||
overflow-y: scroll;
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
`
|
||||
@@ -136,12 +133,7 @@ const TopicListItem = styled.div`
|
||||
&:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
}
|
||||
&.active-left {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
&.active-right {
|
||||
&.active {
|
||||
background-color: var(--color-background-mute);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
CloudOutlined,
|
||||
CodeSandboxOutlined,
|
||||
InfoCircleOutlined,
|
||||
MessageOutlined,
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { CloudOutlined, InfoCircleOutlined, MessageOutlined, SettingOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import { isLocalAi } from '@renderer/config/env'
|
||||
import { FC } from 'react'
|
||||
@@ -41,7 +35,7 @@ const SettingsPage: FC = () => {
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to="/settings/model">
|
||||
<MenuItem className={isRoute('/settings/model')}>
|
||||
<CodeSandboxOutlined />
|
||||
<i className="iconfont icon-ai-model" />
|
||||
{t('settings.model')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
@@ -122,6 +116,11 @@ const MenuItem = styled.li`
|
||||
font-size: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 18px;
|
||||
opacity: 0.7;
|
||||
margin-left: -1px;
|
||||
}
|
||||
&:hover {
|
||||
background: var(--color-background-soft);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk
|
||||
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES } from '@renderer/services/event'
|
||||
import { filterContextMessages, filterMessages } from '@renderer/services/messages'
|
||||
import { Assistant, Message, Provider, Suggestion } from '@renderer/types'
|
||||
import { first, sum, takeRight } from 'lodash'
|
||||
import OpenAI from 'openai'
|
||||
@@ -26,7 +27,7 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
const model = assistant.model || defaultModel
|
||||
const { contextCount, maxTokens } = getAssistantSettings(assistant)
|
||||
|
||||
const userMessages = takeRight(messages, contextCount + 2).map((message) => {
|
||||
const userMessages = filterMessages(filterContextMessages(takeRight(messages, contextCount + 2))).map((message) => {
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.content
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { GoogleGenerativeAI } from '@google/generative-ai'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES } from '@renderer/services/event'
|
||||
import { filterContextMessages, filterMessages } from '@renderer/services/messages'
|
||||
import { Assistant, Message, Provider, Suggestion } from '@renderer/types'
|
||||
import axios from 'axios'
|
||||
import { isEmpty, takeRight } from 'lodash'
|
||||
@@ -25,7 +26,7 @@ export default class GeminiProvider extends BaseProvider {
|
||||
const model = assistant.model || defaultModel
|
||||
const { contextCount, maxTokens } = getAssistantSettings(assistant)
|
||||
|
||||
const userMessages = takeRight(messages, contextCount + 1).map((message) => {
|
||||
const userMessages = filterMessages(filterContextMessages(takeRight(messages, contextCount + 1))).map((message) => {
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.content
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { isLocalAi } from '@renderer/config/env'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES } from '@renderer/services/event'
|
||||
import { filterContextMessages, filterMessages } from '@renderer/services/messages'
|
||||
import { Assistant, Message, Provider, Suggestion } from '@renderer/types'
|
||||
import { fileToBase64, removeQuotes } from '@renderer/utils'
|
||||
import { first, takeRight } from 'lodash'
|
||||
@@ -60,7 +61,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
|
||||
const userMessages: ChatCompletionMessageParam[] = []
|
||||
|
||||
for (const message of takeRight(messages, contextCount + 1)) {
|
||||
for (const message of filterMessages(filterContextMessages(takeRight(messages, contextCount + 1)))) {
|
||||
userMessages.push({
|
||||
role: message.role,
|
||||
content: await this.getMessageContent(message)
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function fetchChatCompletion({
|
||||
}, 1000)
|
||||
|
||||
try {
|
||||
await AI.completions(filterMessages(messages), assistant, ({ text, usage }) => {
|
||||
await AI.completions(messages, assistant, ({ text, usage }) => {
|
||||
message.content = message.content + text || ''
|
||||
message.usage = usage
|
||||
onResponse({ ...message, status: 'pending' })
|
||||
@@ -153,7 +153,7 @@ export async function fetchSuggestions({
|
||||
}
|
||||
|
||||
try {
|
||||
return await AI.suggestions(messages, assistant)
|
||||
return await AI.suggestions(filterMessages(messages), assistant)
|
||||
} catch (error: any) {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ export const EVENT_NAMES = {
|
||||
ESTIMATED_TOKEN_COUNT: 'ESTIMATED_TOKEN_COUNT',
|
||||
SHOW_CHAT_SETTINGS: 'SHOW_CHAT_SETTINGS',
|
||||
SHOW_TOPIC_SIDEBAR: 'SHOW_TOPIC_SIDEBAR',
|
||||
SWITCH_TOPIC_SIDEBAR: 'SWITCH_TOPIC_SIDEBAR'
|
||||
SWITCH_TOPIC_SIDEBAR: 'SWITCH_TOPIC_SIDEBAR',
|
||||
NEW_CONTEXT: 'NEW_CONTEXT'
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
|
||||
import { Assistant, Message } from '@renderer/types'
|
||||
import { GPTTokens } from 'gpt-tokens'
|
||||
import { isEmpty, takeRight } from 'lodash'
|
||||
@@ -5,7 +6,32 @@ import { isEmpty, takeRight } from 'lodash'
|
||||
import { getAssistantSettings } from './assistant'
|
||||
|
||||
export const filterMessages = (messages: Message[]) => {
|
||||
return messages.filter((message) => message.type !== '@').filter((message) => !isEmpty(message.content.trim()))
|
||||
return messages
|
||||
.filter((message) => !['@', 'clear'].includes(message.type!))
|
||||
.filter((message) => !isEmpty(message.content.trim()))
|
||||
}
|
||||
|
||||
export function filterContextMessages(messages: Message[]): Message[] {
|
||||
const clearIndex = messages.findLastIndex((message) => message.type === 'clear')
|
||||
|
||||
if (clearIndex === -1) {
|
||||
return messages
|
||||
}
|
||||
|
||||
return messages.slice(clearIndex + 1)
|
||||
}
|
||||
|
||||
export function getContextCount(assistant: Assistant, messages: Message[]) {
|
||||
const contextCount = assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT
|
||||
const _messages = takeRight(messages, contextCount)
|
||||
const clearIndex = _messages.findLastIndex((message) => message.type === 'clear')
|
||||
const messagesCount = _messages.length
|
||||
|
||||
if (clearIndex === -1) {
|
||||
return contextCount
|
||||
}
|
||||
|
||||
return messagesCount - (clearIndex + 1)
|
||||
}
|
||||
|
||||
export function estimateInputTokenCount(text: string) {
|
||||
@@ -24,7 +50,7 @@ export function estimateHistoryTokenCount(assistant: Assistant, msgs: Message[])
|
||||
model: 'gpt-4o',
|
||||
messages: [
|
||||
{ role: 'system', content: assistant.prompt },
|
||||
...filterMessages(takeRight(msgs, contextCount)).map((message) => ({
|
||||
...filterMessages(filterContextMessages(takeRight(msgs, contextCount))).map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
|
||||
@@ -30,7 +30,7 @@ export type Message = {
|
||||
files?: File[]
|
||||
images?: string[]
|
||||
usage?: OpenAI.Completions.CompletionUsage
|
||||
type?: 'text' | '@'
|
||||
type?: 'text' | '@' | 'clear'
|
||||
}
|
||||
|
||||
export type Topic = {
|
||||
|
||||
Reference in New Issue
Block a user