fix: assistant settings bugs
This commit is contained in:
parent
4169a2ef35
commit
c5965dc696
@ -1,3 +1,2 @@
|
|||||||
export const DEFAULT_TEMPERATURE = 0.7
|
export const DEFAULT_TEMPERATURE = 0.7
|
||||||
export const DEFAULT_MAXTOKENS = 800
|
|
||||||
export const DEFAULT_CONEXTCOUNT = 5
|
export const DEFAULT_CONEXTCOUNT = 5
|
||||||
|
|||||||
@ -66,11 +66,10 @@ const resources = {
|
|||||||
'input.send': 'Send',
|
'input.send': 'Send',
|
||||||
'input.pause': 'Pause',
|
'input.pause': 'Pause',
|
||||||
'input.settings': 'Settings',
|
'input.settings': 'Settings',
|
||||||
|
'input.estimated_tokens': 'Estimated tokens: ',
|
||||||
'settings.temperature': 'Temperature',
|
'settings.temperature': 'Temperature',
|
||||||
'settings.temperature.tip':
|
'settings.temperature.tip':
|
||||||
'Lower values make the model more creative and unpredictable, while higher values make it more deterministic and precise.',
|
'Lower values make the model more creative and unpredictable, while higher values make it more deterministic and precise.',
|
||||||
'settings.max_tokens': 'Max Tokens',
|
|
||||||
'settings.max_tokens.tip': 'The maximum number of tokens to generate in the completion.',
|
|
||||||
'settings.conext_count': 'Context',
|
'settings.conext_count': 'Context',
|
||||||
'settings.conext_count.tip': 'The number of previous messages to keep in the context.',
|
'settings.conext_count.tip': 'The number of previous messages to keep in the context.',
|
||||||
'settings.reset': 'Reset',
|
'settings.reset': 'Reset',
|
||||||
@ -200,12 +199,10 @@ const resources = {
|
|||||||
'input.send': '发送',
|
'input.send': '发送',
|
||||||
'input.pause': '暂停',
|
'input.pause': '暂停',
|
||||||
'input.settings': '设置',
|
'input.settings': '设置',
|
||||||
|
'input.estimated_tokens': '预估消耗',
|
||||||
'settings.temperature': '模型温度',
|
'settings.temperature': '模型温度',
|
||||||
'settings.temperature.tip':
|
'settings.temperature.tip':
|
||||||
'模型生成文本的随机程度。值越大,回复内容越赋有多样性、创造性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7',
|
'模型生成文本的随机程度。值越大,回复内容越赋有多样性、创造性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7',
|
||||||
'settings.max_tokens': '最大回复',
|
|
||||||
'settings.max_tokens.tip':
|
|
||||||
'最大回复内容多少,数值越大,生成的文本越长。普通聊天建议 500-800;短文生成建议 800-2000;代码生成建议 2000-3600;长文生成建议切换模型到 4000 左右',
|
|
||||||
'settings.conext_count': '上下文数',
|
'settings.conext_count': '上下文数',
|
||||||
'settings.conext_count.tip':
|
'settings.conext_count.tip':
|
||||||
'要保留在上下文中的消息数量,数值越大,上下文越长,消耗的 token 越多。普通聊天建议 5-10,代码生成建议 5-10',
|
'要保留在上下文中的消息数量,数值越大,上下文越长,消耗的 token 越多。普通聊天建议 5-10,代码生成建议 5-10',
|
||||||
@ -233,7 +230,7 @@ const resources = {
|
|||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: '设置',
|
title: '设置',
|
||||||
general: '常规',
|
general: '常规设置',
|
||||||
provider: '模型提供商',
|
provider: '模型提供商',
|
||||||
model: '模型设置',
|
model: '模型设置',
|
||||||
assistant: '默认助手',
|
assistant: '默认助手',
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { QuestionCircleOutlined } from '@ant-design/icons'
|
import { QuestionCircleOutlined } from '@ant-design/icons'
|
||||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAXTOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
import { Assistant } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
import { Button, Col, InputNumber, Popover, Row, Slider, Tooltip } from 'antd'
|
import { Button, Col, InputNumber, Popover, Row, Slider, Tooltip } from 'antd'
|
||||||
import { FC, PropsWithChildren, useState } from 'react'
|
import { debounce } from 'lodash'
|
||||||
|
import { FC, PropsWithChildren, useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -14,29 +15,26 @@ interface Props {
|
|||||||
const PopoverContent: FC<Props> = ({ assistant }) => {
|
const PopoverContent: FC<Props> = ({ assistant }) => {
|
||||||
const { updateAssistant } = useAssistants()
|
const { updateAssistant } = useAssistants()
|
||||||
const [temperature, setTemperature] = useState(assistant.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
const [temperature, setTemperature] = useState(assistant.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||||
const [maxTokens, setMaxTokens] = useState(assistant.settings?.maxTokens ?? DEFAULT_MAXTOKENS)
|
|
||||||
const [contextCount, setConextCount] = useState(assistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
const [contextCount, setConextCount] = useState(assistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onUpdateAssistantSettings = ({
|
const onUpdateAssistantSettings = useCallback(
|
||||||
_temperature,
|
debounce(
|
||||||
_maxTokens,
|
({ _temperature, _contextCount }: { _temperature?: number; _contextCount?: number }) => {
|
||||||
_contextCount
|
updateAssistant({
|
||||||
}: {
|
...assistant,
|
||||||
_temperature?: number
|
settings: {
|
||||||
_maxTokens?: number
|
...assistant.settings,
|
||||||
_contextCount?: number
|
temperature: _temperature ?? temperature,
|
||||||
}) => {
|
contextCount: _contextCount ?? contextCount
|
||||||
updateAssistant({
|
}
|
||||||
...assistant,
|
})
|
||||||
settings: {
|
},
|
||||||
...assistant.settings,
|
1000,
|
||||||
temperature: _temperature ?? temperature,
|
{ leading: false, trailing: true }
|
||||||
maxTokens: _maxTokens ?? maxTokens,
|
),
|
||||||
contextCount: _contextCount ?? contextCount
|
[]
|
||||||
}
|
)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTemperatureChange = (value) => {
|
const onTemperatureChange = (value) => {
|
||||||
if (!isNaN(value as number)) {
|
if (!isNaN(value as number)) {
|
||||||
@ -45,13 +43,6 @@ const PopoverContent: FC<Props> = ({ assistant }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMaxTokensChange = (value) => {
|
|
||||||
if (!isNaN(value as number)) {
|
|
||||||
setMaxTokens(value)
|
|
||||||
onUpdateAssistantSettings({ _maxTokens: value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onConextCountChange = (value) => {
|
const onConextCountChange = (value) => {
|
||||||
if (!isNaN(value as number)) {
|
if (!isNaN(value as number)) {
|
||||||
setConextCount(value)
|
setConextCount(value)
|
||||||
@ -61,24 +52,27 @@ const PopoverContent: FC<Props> = ({ assistant }) => {
|
|||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
setTemperature(DEFAULT_TEMPERATURE)
|
setTemperature(DEFAULT_TEMPERATURE)
|
||||||
setMaxTokens(DEFAULT_MAXTOKENS)
|
|
||||||
setConextCount(DEFAULT_CONEXTCOUNT)
|
setConextCount(DEFAULT_CONEXTCOUNT)
|
||||||
updateAssistant({
|
updateAssistant({
|
||||||
...assistant,
|
...assistant,
|
||||||
settings: {
|
settings: {
|
||||||
...assistant.settings,
|
...assistant.settings,
|
||||||
temperature: DEFAULT_TEMPERATURE,
|
temperature: DEFAULT_TEMPERATURE,
|
||||||
maxTokens: DEFAULT_MAXTOKENS,
|
|
||||||
contextCount: DEFAULT_CONEXTCOUNT
|
contextCount: DEFAULT_CONEXTCOUNT
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTemperature(assistant.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||||
|
setConextCount(assistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||||
|
}, [assistant])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}>
|
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}>
|
||||||
<Col span={5}>
|
<Col span={6}>
|
||||||
<Row align="middle">
|
<Row align="middle" justify="end">
|
||||||
<Label>{t('assistant.settings.temperature')}</Label>
|
<Label>{t('assistant.settings.temperature')}</Label>
|
||||||
<Tooltip title={t('assistant.settings.temperature.tip')}>
|
<Tooltip title={t('assistant.settings.temperature.tip')}>
|
||||||
<QuestionIcon />
|
<QuestionIcon />
|
||||||
@ -95,11 +89,11 @@ const PopoverContent: FC<Props> = ({ assistant }) => {
|
|||||||
step={0.1}
|
step={0.1}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col span={3}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
min={0}
|
min={0}
|
||||||
max={1.2}
|
max={1.2}
|
||||||
style={{ width: 70, marginLeft: 5, textAlign: 'center' }}
|
style={{ width: 50, marginLeft: 5, textAlign: 'center' }}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
value={temperature}
|
value={temperature}
|
||||||
onChange={onTemperatureChange}
|
onChange={onTemperatureChange}
|
||||||
@ -108,8 +102,8 @@ const PopoverContent: FC<Props> = ({ assistant }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}>
|
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}>
|
||||||
<Col span={5}>
|
<Col span={6}>
|
||||||
<Row align="middle">
|
<Row align="middle" justify="end">
|
||||||
<Label>{t('assistant.settings.conext_count')}</Label>
|
<Label>{t('assistant.settings.conext_count')}</Label>
|
||||||
<Tooltip title={t('assistant.settings.conext_count.tip')}>
|
<Tooltip title={t('assistant.settings.conext_count.tip')}>
|
||||||
<QuestionIcon />
|
<QuestionIcon />
|
||||||
@ -126,11 +120,11 @@ const PopoverContent: FC<Props> = ({ assistant }) => {
|
|||||||
step={1}
|
step={1}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col span={3}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
min={0}
|
min={0}
|
||||||
max={20}
|
max={20}
|
||||||
style={{ width: 70, marginLeft: 5, textAlign: 'center' }}
|
style={{ width: 50, marginLeft: 5, textAlign: 'center' }}
|
||||||
step={1}
|
step={1}
|
||||||
value={contextCount}
|
value={contextCount}
|
||||||
onChange={onConextCountChange}
|
onChange={onConextCountChange}
|
||||||
@ -138,41 +132,8 @@ const PopoverContent: FC<Props> = ({ assistant }) => {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="middle" gutter={20}>
|
<Row justify="center">
|
||||||
<Col span={5}>
|
<Button onClick={onReset}>{t('assistant.settings.reset')}</Button>
|
||||||
<Row align="middle">
|
|
||||||
<Label>{t('assistant.settings.max_tokens')}</Label>
|
|
||||||
<Tooltip title={t('assistant.settings.max_tokens.tip')}>
|
|
||||||
<QuestionIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
<Col span={14}>
|
|
||||||
<Slider
|
|
||||||
min={0}
|
|
||||||
max={5000}
|
|
||||||
onChange={onMaxTokensChange}
|
|
||||||
value={typeof maxTokens === 'number' ? maxTokens : 0}
|
|
||||||
marks={{ 0: '0', 800: '800', 2000: '2000', 3600: '3600', 5000: t('assistant.settings.max') }}
|
|
||||||
step={64}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col span={4}>
|
|
||||||
<InputNumber
|
|
||||||
min={0}
|
|
||||||
max={5000}
|
|
||||||
style={{ width: 70, marginLeft: 5, textAlign: 'center' }}
|
|
||||||
step={64}
|
|
||||||
value={maxTokens}
|
|
||||||
onChange={onMaxTokensChange}
|
|
||||||
controls={false}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row justify="center" style={{ marginTop: 10 }}>
|
|
||||||
<Button onClick={onReset} style={{ marginRight: 10 }}>
|
|
||||||
{t('assistant.settings.reset')}
|
|
||||||
</Button>
|
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
@ -199,7 +160,7 @@ const Container = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
width: 500px;
|
width: 420px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@ -24,11 +24,9 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onDelete = (assistant: Assistant) => {
|
const onDelete = (assistant: Assistant) => {
|
||||||
|
const _assistant = last(assistants.filter((a) => a.id !== assistant.id))
|
||||||
|
_assistant ? setActiveAssistant(_assistant) : onCreateAssistant()
|
||||||
removeAssistant(assistant.id)
|
removeAssistant(assistant.id)
|
||||||
setTimeout(() => {
|
|
||||||
const _assistant = last(assistants.filter((a) => a.id !== assistant.id))
|
|
||||||
_assistant ? setActiveAssistant(_assistant) : onCreateAssistant()
|
|
||||||
}, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Assistant, Message } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
import { FC, useRef } from 'react'
|
import { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import Inputbar from './Inputbar'
|
import Inputbar from './Inputbar'
|
||||||
import Messages from './Messages'
|
import Messages from './Messages'
|
||||||
@ -15,17 +15,12 @@ interface Props {
|
|||||||
const Chat: FC<Props> = (props) => {
|
const Chat: FC<Props> = (props) => {
|
||||||
const { assistant } = useAssistant(props.assistant.id)
|
const { assistant } = useAssistant(props.assistant.id)
|
||||||
const { activeTopic, setActiveTopic } = useActiveTopic(assistant)
|
const { activeTopic, setActiveTopic } = useActiveTopic(assistant)
|
||||||
const messagesRef = useRef<Message[]>([])
|
|
||||||
|
|
||||||
if (!assistant) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="chat">
|
<Container id="chat">
|
||||||
<Flex vertical flex={1} justify="space-between">
|
<Flex vertical flex={1} justify="space-between">
|
||||||
<Messages assistant={assistant} topic={activeTopic} messagesRef={messagesRef} />
|
<Messages assistant={assistant} topic={activeTopic} />
|
||||||
<Inputbar assistant={assistant} setActiveTopic={setActiveTopic} messagesRef={messagesRef} />
|
<Inputbar assistant={assistant} setActiveTopic={setActiveTopic} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Topics assistant={assistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
|
<Topics assistant={assistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
import { Assistant, Message, Topic } from '@renderer/types'
|
import { Assistant, Message, Topic } from '@renderer/types'
|
||||||
import { estimateTokenCount, uuid } from '@renderer/utils'
|
import { estimateInputTokenCount, uuid } from '@renderer/utils'
|
||||||
import { FC, MutableRefObject, useCallback, useEffect, useRef, useState } from 'react'
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { MoreOutlined } from '@ant-design/icons'
|
import { MoreOutlined } from '@ant-design/icons'
|
||||||
import { Button, Popconfirm, Tooltip } from 'antd'
|
import { Button, Popconfirm, Tooltip } from 'antd'
|
||||||
@ -9,15 +9,15 @@ import { useShowRightSidebar } from '@renderer/hooks/useStore'
|
|||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import {
|
import {
|
||||||
ClearOutlined,
|
ClearOutlined,
|
||||||
|
ControlOutlined,
|
||||||
FullscreenExitOutlined,
|
FullscreenExitOutlined,
|
||||||
FullscreenOutlined,
|
FullscreenOutlined,
|
||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
PlusCircleOutlined,
|
PlusCircleOutlined
|
||||||
SettingOutlined
|
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||||
import { isEmpty } from 'lodash'
|
import { debounce, isEmpty } from 'lodash'
|
||||||
import SendMessageSetting from './SendMessageSetting'
|
import SendMessageSetting from './SendMessageSetting'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@ -30,15 +30,15 @@ import AssistantSettings from './AssistantSettings'
|
|||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
setActiveTopic: (topic: Topic) => void
|
setActiveTopic: (topic: Topic) => void
|
||||||
messagesRef: MutableRefObject<Message[]>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Inputbar: FC<Props> = ({ assistant, setActiveTopic, messagesRef }) => {
|
const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||||
const [text, setText] = useState('')
|
const [text, setText] = useState('')
|
||||||
const { setShowRightSidebar } = useShowRightSidebar()
|
const { setShowRightSidebar } = useShowRightSidebar()
|
||||||
const { addTopic } = useAssistant(assistant.id)
|
const { addTopic } = useAssistant(assistant.id)
|
||||||
const { sendMessageShortcut } = useSettings()
|
const { sendMessageShortcut } = useSettings()
|
||||||
const [expended, setExpend] = useState(false)
|
const [expended, setExpend] = useState(false)
|
||||||
|
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
||||||
const generating = useAppSelector((state) => state.runtime.generating)
|
const generating = useAppSelector((state) => state.runtime.generating)
|
||||||
const inputRef = useRef<TextAreaRef>(null)
|
const inputRef = useRef<TextAreaRef>(null)
|
||||||
|
|
||||||
@ -68,6 +68,8 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, messagesRef }) => {
|
|||||||
setText('')
|
setText('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inputTokenCount = useMemo(() => estimateInputTokenCount(text), [text])
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (sendMessageShortcut === 'Enter' && event.key === 'Enter') {
|
if (sendMessageShortcut === 'Enter' && event.key === 'Enter') {
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
@ -96,8 +98,6 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, messagesRef }) => {
|
|||||||
store.dispatch(setGenerating(false))
|
store.dispatch(setGenerating(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
const textCount = text.length === 0 ? '' : (text: string) => estimateTokenCount(text, assistant, messagesRef.current)
|
|
||||||
|
|
||||||
// Command or Ctrl + N create new topic
|
// Command or Ctrl + N create new topic
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeydown = (e) => {
|
const onKeydown = (e) => {
|
||||||
@ -113,11 +113,13 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, messagesRef }) => {
|
|||||||
}, [addNewTopic, generating])
|
}, [addNewTopic, generating])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const _setEstimateTokenCount = debounce(setEstimateTokenCount, 100, { leading: false, trailing: true })
|
||||||
const unsubscribes = [
|
const unsubscribes = [
|
||||||
EventEmitter.on(EVENT_NAMES.EDIT_MESSAGE, (message: Message) => {
|
EventEmitter.on(EVENT_NAMES.EDIT_MESSAGE, (message: Message) => {
|
||||||
setText(message.content)
|
setText(message.content)
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
})
|
}),
|
||||||
|
EventEmitter.on(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, _setEstimateTokenCount)
|
||||||
]
|
]
|
||||||
return () => unsubscribes.forEach((unsub) => unsub())
|
return () => unsubscribes.forEach((unsub) => unsub())
|
||||||
}, [])
|
}, [])
|
||||||
@ -155,7 +157,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, messagesRef }) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<AssistantSettings assistant={assistant}>
|
<AssistantSettings assistant={assistant}>
|
||||||
<ToolbarButton type="text">
|
<ToolbarButton type="text">
|
||||||
<SettingOutlined />
|
<ControlOutlined />
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</AssistantSettings>
|
</AssistantSettings>
|
||||||
<Tooltip placement="top" title={expended ? t('assistant.input.collapse') : t('assistant.input.expand')} arrow>
|
<Tooltip placement="top" title={expended ? t('assistant.input.collapse') : t('assistant.input.expand')} arrow>
|
||||||
@ -188,19 +190,12 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, messagesRef }) => {
|
|||||||
contextMenu="true"
|
contextMenu="true"
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
showCount
|
showCount
|
||||||
count={{ strategy: textCount }}
|
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
styles={{
|
styles={{ textarea: { paddingLeft: 0 } }}
|
||||||
textarea: { paddingLeft: 0 },
|
|
||||||
count: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: 5,
|
|
||||||
bottom: 5,
|
|
||||||
fontSize: 11,
|
|
||||||
display: text.length === 0 ? 'none' : 'block'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
<TextCount>
|
||||||
|
{t('assistant.input.estimated_tokens')}: {`${inputTokenCount}/${estimateTokenCount}`}
|
||||||
|
</TextCount>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -213,6 +208,7 @@ const Container = styled.div`
|
|||||||
border-top: 0.5px solid var(--color-border);
|
border-top: 0.5px solid var(--color-border);
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Textarea = styled(TextArea)`
|
const Textarea = styled(TextArea)`
|
||||||
@ -256,4 +252,12 @@ const ToolbarButton = styled(Button)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const TextCount = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
bottom: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
`
|
||||||
|
|
||||||
export default Inputbar
|
export default Inputbar
|
||||||
|
|||||||
@ -104,8 +104,8 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
|||||||
<MessageMetadata>{message.modelId}</MessageMetadata>
|
<MessageMetadata>{message.modelId}</MessageMetadata>
|
||||||
{message.usage && (
|
{message.usage && (
|
||||||
<>
|
<>
|
||||||
<MessageMetadata style={{ textTransform: 'uppercase' }}>
|
<MessageMetadata>
|
||||||
tokens used: {message.usage.total_tokens} (IN:{message.usage.prompt_tokens}/OUT:
|
Tokens: {message.usage.total_tokens} (IN:{message.usage.prompt_tokens}/OUT:
|
||||||
{message.usage.completion_tokens})
|
{message.usage.completion_tokens})
|
||||||
</MessageMetadata>
|
</MessageMetadata>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
import { Assistant, Message, Topic } from '@renderer/types'
|
import { Assistant, Message, Topic } from '@renderer/types'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { FC, MutableRefObject, useCallback, useEffect, useRef, useState } from 'react'
|
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import MessageItem from './Message'
|
import MessageItem from './Message'
|
||||||
import { reverse } from 'lodash'
|
import { reverse } from 'lodash'
|
||||||
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { estimateHistoryTokenCount, runAsyncFunction } from '@renderer/utils'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
import LocalStorage from '@renderer/services/storage'
|
||||||
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
@ -15,10 +15,9 @@ import { t } from 'i18next'
|
|||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
topic: Topic
|
topic: Topic
|
||||||
messagesRef: MutableRefObject<Message[]>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Messages: FC<Props> = ({ assistant, topic, messagesRef }) => {
|
const Messages: FC<Props> = ({ assistant, topic }) => {
|
||||||
const [messages, setMessages] = useState<Message[]>([])
|
const [messages, setMessages] = useState<Message[]>([])
|
||||||
const [lastMessage, setLastMessage] = useState<Message | null>(null)
|
const [lastMessage, setLastMessage] = useState<Message | null>(null)
|
||||||
const { updateTopic } = useAssistant(assistant.id)
|
const { updateTopic } = useAssistant(assistant.id)
|
||||||
@ -97,8 +96,11 @@ const Messages: FC<Props> = ({ assistant, topic, messagesRef }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
containerRef.current?.scrollTo({ top: 100000, behavior: 'auto' })
|
containerRef.current?.scrollTo({ top: 100000, behavior: 'auto' })
|
||||||
messagesRef.current = messages
|
}, [messages])
|
||||||
}, [messages, messagesRef])
|
|
||||||
|
useEffect(() => {
|
||||||
|
EventEmitter.emit(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, estimateHistoryTokenCount(assistant, messages))
|
||||||
|
}, [assistant, messages])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="messages" key={assistant.id} ref={containerRef}>
|
<Container id="messages" key={assistant.id} ref={containerRef}>
|
||||||
|
|||||||
@ -82,12 +82,8 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showRightSidebar) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={showRightSidebar ? '' : 'collapsed'}>
|
<Container style={{ display: showRightSidebar ? 'block' : 'none' }}>
|
||||||
<TopicTitle>
|
<TopicTitle>
|
||||||
<span>
|
<span>
|
||||||
{t('assistant.topics.title')} ({assistant.topics.length})
|
{t('assistant.topics.title')} ({assistant.topics.length})
|
||||||
|
|||||||
@ -1,40 +1,38 @@
|
|||||||
import { QuestionCircleOutlined } from '@ant-design/icons'
|
import { QuestionCircleOutlined } from '@ant-design/icons'
|
||||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAXTOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||||
import { useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
import { useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { Button, Col, Input, InputNumber, Row, Slider, Tooltip } from 'antd'
|
import { Button, Col, Input, InputNumber, Row, Slider, Tooltip } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { SettingContainer, SettingDivider, SettingSubtitle, SettingTitle } from './components'
|
import { SettingContainer, SettingDivider, SettingSubtitle, SettingTitle } from './components'
|
||||||
|
import { debounce } from 'lodash'
|
||||||
|
|
||||||
const AssistantSettings: FC = () => {
|
const AssistantSettings: FC = () => {
|
||||||
const { defaultAssistant, updateDefaultAssistant } = useDefaultAssistant()
|
const { defaultAssistant, updateDefaultAssistant } = useDefaultAssistant()
|
||||||
const [temperature, setTemperature] = useState(defaultAssistant.settings?.temperature || DEFAULT_TEMPERATURE)
|
const [temperature, setTemperature] = useState(defaultAssistant.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||||
const [maxTokens, setMaxTokens] = useState(defaultAssistant.settings?.maxTokens || DEFAULT_MAXTOKENS)
|
const [contextCount, setConextCount] = useState(defaultAssistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||||
const [contextCount, setConextCount] = useState(defaultAssistant.settings?.contextCount || DEFAULT_CONEXTCOUNT)
|
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onUpdateAssistantSettings = ({
|
const onUpdateAssistantSettings = useCallback(
|
||||||
_temperature,
|
debounce(
|
||||||
_maxTokens,
|
({ _temperature, _contextCount }: { _temperature?: number; _contextCount?: number }) => {
|
||||||
_contextCount
|
updateDefaultAssistant({
|
||||||
}: {
|
...defaultAssistant,
|
||||||
_temperature?: number
|
settings: {
|
||||||
_maxTokens?: number
|
...defaultAssistant.settings,
|
||||||
_contextCount?: number
|
temperature: _temperature ?? temperature,
|
||||||
}) => {
|
contextCount: _contextCount ?? contextCount
|
||||||
updateDefaultAssistant({
|
}
|
||||||
...defaultAssistant,
|
})
|
||||||
settings: {
|
},
|
||||||
...defaultAssistant.settings,
|
1000,
|
||||||
temperature: _temperature || temperature,
|
{ leading: false, trailing: true }
|
||||||
maxTokens: _maxTokens || maxTokens,
|
),
|
||||||
contextCount: _contextCount || contextCount
|
[]
|
||||||
}
|
)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTemperatureChange = (value) => {
|
const onTemperatureChange = (value) => {
|
||||||
if (!isNaN(value as number)) {
|
if (!isNaN(value as number)) {
|
||||||
@ -43,13 +41,6 @@ const AssistantSettings: FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMaxTokensChange = (value) => {
|
|
||||||
if (!isNaN(value as number)) {
|
|
||||||
setMaxTokens(value)
|
|
||||||
onUpdateAssistantSettings({ _maxTokens: value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onConextCountChange = (value) => {
|
const onConextCountChange = (value) => {
|
||||||
if (!isNaN(value as number)) {
|
if (!isNaN(value as number)) {
|
||||||
setConextCount(value)
|
setConextCount(value)
|
||||||
@ -59,14 +50,12 @@ const AssistantSettings: FC = () => {
|
|||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
setTemperature(DEFAULT_TEMPERATURE)
|
setTemperature(DEFAULT_TEMPERATURE)
|
||||||
setMaxTokens(DEFAULT_MAXTOKENS)
|
|
||||||
setConextCount(DEFAULT_CONEXTCOUNT)
|
setConextCount(DEFAULT_CONEXTCOUNT)
|
||||||
updateDefaultAssistant({
|
updateDefaultAssistant({
|
||||||
...defaultAssistant,
|
...defaultAssistant,
|
||||||
settings: {
|
settings: {
|
||||||
...defaultAssistant.settings,
|
...defaultAssistant.settings,
|
||||||
temperature: DEFAULT_TEMPERATURE,
|
temperature: DEFAULT_TEMPERATURE,
|
||||||
maxTokens: DEFAULT_MAXTOKENS,
|
|
||||||
contextCount: DEFAULT_CONEXTCOUNT
|
contextCount: DEFAULT_CONEXTCOUNT
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -147,35 +136,7 @@ const AssistantSettings: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="middle">
|
<Button onClick={onReset} style={{ width: 100 }}>
|
||||||
<Label>{t('assistant.settings.max_tokens')}</Label>
|
|
||||||
<Tooltip title={t('assistant.settings.max_tokens.tip')}>
|
|
||||||
<QuestionIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</Row>
|
|
||||||
<Row align="middle" gutter={20}>
|
|
||||||
<Col span={22}>
|
|
||||||
<Slider
|
|
||||||
min={0}
|
|
||||||
max={5000}
|
|
||||||
onChange={onMaxTokensChange}
|
|
||||||
value={typeof maxTokens === 'number' ? maxTokens : 0}
|
|
||||||
marks={{ 0: '0', 800: '800', 2000: '2000', 3600: '3600', 5000: t('assistant.settings.max') }}
|
|
||||||
step={64}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col span={2}>
|
|
||||||
<InputNumber
|
|
||||||
min={0}
|
|
||||||
max={5000}
|
|
||||||
step={100}
|
|
||||||
value={maxTokens}
|
|
||||||
onChange={onMaxTokensChange}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Button onClick={onReset} style={{ width: 100, marginTop: 20 }}>
|
|
||||||
{t('assistant.settings.reset')}
|
{t('assistant.settings.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
|
|||||||
@ -32,11 +32,11 @@ export default class ProviderSDK {
|
|||||||
) {
|
) {
|
||||||
const defaultModel = getDefaultModel()
|
const defaultModel = getDefaultModel()
|
||||||
const model = assistant.model || defaultModel
|
const model = assistant.model || defaultModel
|
||||||
const { contextCount, maxTokens } = getAssistantSettings(assistant)
|
const { contextCount } = getAssistantSettings(assistant)
|
||||||
|
|
||||||
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
||||||
|
|
||||||
const userMessages = takeRight(messages, contextCount).map((message) => ({
|
const userMessages = takeRight(messages, contextCount + 1).map((message) => ({
|
||||||
role: message.role,
|
role: message.role,
|
||||||
content: message.content
|
content: message.content
|
||||||
}))
|
}))
|
||||||
@ -46,7 +46,7 @@ export default class ProviderSDK {
|
|||||||
.stream({
|
.stream({
|
||||||
model: model.id,
|
model: model.id,
|
||||||
messages: [systemMessage, ...userMessages].filter(Boolean) as MessageParam[],
|
messages: [systemMessage, ...userMessages].filter(Boolean) as MessageParam[],
|
||||||
max_tokens: assistant.settings?.maxTokens || maxTokens,
|
max_tokens: 4096,
|
||||||
temperature: assistant.settings?.temperature
|
temperature: assistant.settings?.temperature
|
||||||
})
|
})
|
||||||
.on('text', (text) => onChunk({ text: text || '' }))
|
.on('text', (text) => onChunk({ text: text || '' }))
|
||||||
@ -64,7 +64,6 @@ export default class ProviderSDK {
|
|||||||
model: model.id,
|
model: model.id,
|
||||||
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
|
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
|
||||||
stream: true,
|
stream: true,
|
||||||
max_tokens: assistant.settings?.maxTokens,
|
|
||||||
temperature: assistant.settings?.temperature
|
temperature: assistant.settings?.temperature
|
||||||
})
|
})
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
|
|||||||
@ -10,5 +10,6 @@ export const EVENT_NAMES = {
|
|||||||
ADD_ASSISTANT: 'ADD_ASSISTANT',
|
ADD_ASSISTANT: 'ADD_ASSISTANT',
|
||||||
EDIT_MESSAGE: 'EDIT_MESSAGE',
|
EDIT_MESSAGE: 'EDIT_MESSAGE',
|
||||||
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
|
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
|
||||||
CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED'
|
CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED',
|
||||||
|
ESTIMATED_TOKEN_COUNT: 'ESTIMATED_TOKEN_COUNT'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ export type Assistant = {
|
|||||||
export type AssistantSettings = {
|
export type AssistantSettings = {
|
||||||
contextCount: number
|
contextCount: number
|
||||||
temperature: number
|
temperature: number
|
||||||
maxTokens: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid'
|
|||||||
import imageCompression from 'browser-image-compression'
|
import imageCompression from 'browser-image-compression'
|
||||||
import { Assistant, AssistantSettings, Message, Model } from '@renderer/types'
|
import { Assistant, AssistantSettings, Message, Model } from '@renderer/types'
|
||||||
import { GPTTokens } from 'gpt-tokens'
|
import { GPTTokens } from 'gpt-tokens'
|
||||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAXTOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||||
import { take } from 'lodash'
|
import { takeRight } from 'lodash'
|
||||||
|
|
||||||
export const runAsyncFunction = async (fn: () => void) => {
|
export const runAsyncFunction = async (fn: () => void) => {
|
||||||
await fn()
|
await fn()
|
||||||
@ -169,31 +169,32 @@ export function getFirstCharacter(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getAssistantSettings = (assistant: Assistant): AssistantSettings => {
|
export const getAssistantSettings = (assistant: Assistant): AssistantSettings => {
|
||||||
|
const contextCount = assistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT
|
||||||
return {
|
return {
|
||||||
contextCount: assistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT,
|
contextCount: contextCount === 20 ? 100000 : contextCount,
|
||||||
temperature: assistant.settings?.temperature ?? DEFAULT_TEMPERATURE,
|
temperature: assistant.settings?.temperature ?? DEFAULT_TEMPERATURE
|
||||||
maxTokens: assistant.settings?.maxTokens ?? DEFAULT_MAXTOKENS
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function estimateTokenCount(text: string, assistant: Assistant, msgs: Message[]) {
|
export function estimateInputTokenCount(text: string) {
|
||||||
const { contextCount } = getAssistantSettings(assistant)
|
|
||||||
|
|
||||||
console.debug('contextCount', contextCount)
|
|
||||||
|
|
||||||
const input = new GPTTokens({
|
const input = new GPTTokens({
|
||||||
model: 'gpt-4o',
|
model: 'gpt-4o',
|
||||||
messages: [{ role: 'user', content: text }]
|
messages: [{ role: 'user', content: text }]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return input.usedTokens - 7
|
||||||
|
}
|
||||||
|
|
||||||
|
export function estimateHistoryTokenCount(assistant: Assistant, msgs: Message[]) {
|
||||||
|
const { contextCount } = getAssistantSettings(assistant)
|
||||||
|
|
||||||
const all = new GPTTokens({
|
const all = new GPTTokens({
|
||||||
model: 'gpt-4o',
|
model: 'gpt-4o',
|
||||||
messages: [
|
messages: [
|
||||||
{ role: 'system', content: assistant.prompt },
|
{ role: 'system', content: assistant.prompt },
|
||||||
{ role: 'user', content: text },
|
...takeRight(msgs, contextCount).map((message) => ({ role: message.role, content: message.content }))
|
||||||
...take(msgs, contextCount).map((message) => ({ role: message.role, content: message.content }))
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
return `Token ${input.usedTokens - 7} / ${all.usedTokens}` as unknown as number
|
return all.usedTokens - 7
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user