Compare commits

...

12 Commits

Author SHA1 Message Date
kangfenmao
8d7cde1231 0.3.2 2024-07-22 14:52:54 +08:00
kangfenmao
87c04408de feat: add contextCount to inputbar 2024-07-22 14:50:40 +08:00
kangfenmao
2592448c74 feat: add email to about titles 2024-07-22 14:26:35 +08:00
kangfenmao
6f054874e8 chore: remove change log component 2024-07-22 14:25:15 +08:00
kangfenmao
40d687104e feat: new about page 2024-07-22 14:24:14 +08:00
kangfenmao
ac3cfe2878 fix: disable switch while assistant generating message 2024-07-22 11:28:26 +08:00
kangfenmao
e9a7735fce feat: add updateAssistantSettings to useAssistant hook 2024-07-22 11:15:10 +08:00
kangfenmao
c1a8198575 fix(ProviderSDK): clarify instruction for session summary to avoid punctuation marks and special characters 2024-07-22 10:49:10 +08:00
kangfenmao
8b45548b79 refactor: topic component code 2024-07-22 10:38:00 +08:00
kangfenmao
3f3b930819 fix: disabled switch topic while generating message 2024-07-22 10:22:47 +08:00
kangfenmao
a5d6e2c5c5 0.3.1 2024-07-21 23:44:09 +08:00
kangfenmao
2993ab8dc1 fix: topic missing bug and delete assistant crash 2024-07-21 23:43:17 +08:00
20 changed files with 311 additions and 441 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "cherry-studio", "name": "cherry-studio",
"version": "0.3.0", "version": "0.3.2",
"description": "A powerful AI assistant for producer.", "description": "A powerful AI assistant for producer.",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "kangfenmao@qq.com", "author": "kangfenmao@qq.com",

View File

@@ -1,55 +0,0 @@
# CHANGES LOG
### v0.3.0 - 2024-07-21
- Supports setting the model Temperature parameter
- Support for setting the number of contexts
- Token consumption estimation added to the input box
### v0.2.9 - 2024-07-20
- 📢 Add AiHubMix provider
### v0.2.8 - 2024-07-20
- 🆕 Feature: Add customized service providers
### v0.2.7 - 2024-07-19
- 📢 Add DashScope Provider
- 📢 Add Anthropic Provider
### v0.2.6 - 2024-07-17
- 🆕 Fixed the issue of the BaiChuan API KEY not displaying when clicking to obtain the URL
- 📢 New intelligent body center style
### v0.2.5 - 2024-07-17
- 🆕 Baichuan AI Service Providers
- 📢 New Intelligent Agent Page with Multiple Professional Assistants
- 🌐 Multilingual Issue Fixes and Detailed Optimizations
### v0.2.4 - 2024-07-16
- Fixed the issue of the update log page cannot be scrolled
- Added a check for updates button
### v0.2.3 - 2024-07-16
- Fixed multi-language prompt errors
- Fixed default model error issues with ZHIPU AI
- Fixed OpenRouter API detection error issues
- Fixed multi-language translation errors with model providers
### v0.2.2 - 2024-07-15
- Fix the issue where the default assistant name is empty.
- Fix the problem with default language detection during the first installation.
- Adjust the changelog style.
### v0.2.1 - 2024-07-15
- **Feature**: Add new feature for pausing message sending
- **Fix**: Resolve incomplete translation issue upon language switch
- **Build**: Support for macOS Intel architecture

View File

@@ -1,56 +0,0 @@
# 更新日志
### v0.3.0 - 2024-07-21
- 支持设置模型 Temperature 参数
- 支持设置上下文数量
- 输入框增加 Token 消耗预估
### v0.2.9 - 2024-07-20
- 📢 新增 AiMixHub 服务提供商
### v0.2.8 - 2024-07-20
- 🆕 新功能: 可以添加自定义服务提供商了
### v0.2.7 - 2024-07-19
- 📢 新增阿里云灵积服务商
- 📢 新增 Anthropic 服务商
### v0.2.6 - 2024-07-17
- 🆕 修复百川 API KEY 点击获取网址没有显示问题
- 📢 新的智能体中心样式
### v0.2.5 - 2024-07-17
- 🆕 新增百川AI服务商
- 📢 全新的智能体页面,新增多种职业助手
- 🌐 多语言问题修复,细节优化
### v0.2.4 - 2024-07-16
- 修复更新日志页面不能滚动问题
- 新增检查更新按钮
### v0.2.3 - 2024-07-16
- 修复多语言提示错误
- 修复智谱AI默认模型错误问题
- 修复 OpenRouter API 检测出错问题
- 修复模型提供商多语言翻译错误问题
### v0.2.2 - 2024-07-15
- 修复默认助理名称为空的问题
- 修复首次安装默认语言检测问题
- 更新日志样式微调
### v0.2.1 - 2024-07-15
- 【功能】新增消息暂停发送功能
- 【修复】修复多语言切换不彻底问题
- 【构建】支持 macOS Intel 架构

View File

@@ -1,4 +1,4 @@
import { useAppInitEffect } from '@renderer/hooks/useAppInitEffect' import { useAppInit } from '@renderer/hooks/useAppInit'
import { message, Modal } from 'antd' import { message, Modal } from 'antd'
import { findIndex, pullAt } from 'lodash' import { findIndex, pullAt } from 'lodash'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
@@ -29,7 +29,7 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
const [messageApi, messageContextHolder] = message.useMessage() const [messageApi, messageContextHolder] = message.useMessage()
const [modal, modalContextHolder] = Modal.useModal() const [modal, modalContextHolder] = Modal.useModal()
useAppInitEffect() useAppInit()
onPop = () => { onPop = () => {
const views = [...elements] const views = [...elements]

View File

@@ -5,7 +5,7 @@ import { setAvatar } from '@renderer/store/runtime'
import { runAsyncFunction } from '@renderer/utils' import { runAsyncFunction } from '@renderer/utils'
import { useEffect } from 'react' import { useEffect } from 'react'
export function useAppInitEffect() { export function useAppInit() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useEffect(() => { useEffect(() => {

View File

@@ -1,20 +1,21 @@
import { getDefaultTopic } from '@renderer/services/assistant' import { getDefaultTopic } from '@renderer/services/assistant'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { import {
addTopic as _addTopic,
removeAllTopics as _removeAllTopics,
removeTopic as _removeTopic,
setModel as _setModel,
updateAssistants as _updateAssistants,
updateDefaultAssistant as _updateDefaultAssistant,
updateTopic as _updateTopic,
updateTopics as _updateTopics,
addAssistant, addAssistant,
addTopic,
removeAllTopics,
removeAssistant, removeAssistant,
updateAssistant removeTopic,
setModel,
updateAssistant,
updateAssistants,
updateAssistantSettings,
updateDefaultAssistant,
updateTopic,
updateTopics
} from '@renderer/store/assistants' } from '@renderer/store/assistants'
import { setDefaultModel as _setDefaultModel, setTopicNamingModel as _setTopicNamingModel } from '@renderer/store/llm' import { setDefaultModel as _setDefaultModel, setTopicNamingModel as _setTopicNamingModel } from '@renderer/store/llm'
import { Assistant, Model, Topic } from '@renderer/types' import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
import localforage from 'localforage' import localforage from 'localforage'
export function useAssistants() { export function useAssistants() {
@@ -23,9 +24,8 @@ export function useAssistants() {
return { return {
assistants, assistants,
updateAssistants: (assistants: Assistant[]) => dispatch(_updateAssistants(assistants)), updateAssistants: (assistants: Assistant[]) => dispatch(updateAssistants(assistants)),
addAssistant: (assistant: Assistant) => dispatch(addAssistant(assistant)), addAssistant: (assistant: Assistant) => dispatch(addAssistant(assistant)),
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
removeAssistant: (id: string) => { removeAssistant: (id: string) => {
dispatch(removeAssistant({ id })) dispatch(removeAssistant({ id }))
const assistant = assistants.find((a) => a.id === id) const assistant = assistants.find((a) => a.id === id)
@@ -44,17 +44,21 @@ export function useAssistant(id: string) {
return { return {
assistant, assistant,
model: assistant?.model ?? defaultModel, model: assistant?.model ?? defaultModel,
addTopic: (topic: Topic) => dispatch(_addTopic({ assistantId: assistant.id, topic })), addTopic: (topic: Topic) => dispatch(addTopic({ assistantId: assistant.id, topic })),
removeTopic: (topic: Topic) => dispatch(_removeTopic({ assistantId: assistant.id, topic })), removeTopic: (topic: Topic) => dispatch(removeTopic({ assistantId: assistant.id, topic })),
updateTopic: (topic: Topic) => dispatch(_updateTopic({ assistantId: assistant.id, topic })), updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })),
updateTopics: (topics: Topic[]) => dispatch(_updateTopics({ assistantId: assistant.id, topics })), updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })),
removeAllTopics: () => dispatch(_removeAllTopics({ assistantId: assistant.id })), removeAllTopics: () => dispatch(removeAllTopics({ assistantId: assistant.id })),
setModel: (model: Model) => dispatch(_setModel({ assistantId: assistant.id, model })) setModel: (model: Model) => dispatch(setModel({ assistantId: assistant.id, model })),
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
updateAssistantSettings: (settings: AssistantSettings) => {
dispatch(updateAssistantSettings({ assistantId: assistant.id, settings }))
}
} }
} }
export function useDefaultAssistant() { export function useDefaultAssistant() {
const { defaultAssistant } = useAppSelector((state) => state.assistants) const defaultAssistant = useAppSelector((state) => state.assistants.defaultAssistant)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
return { return {
@@ -62,7 +66,7 @@ export function useDefaultAssistant() {
...defaultAssistant, ...defaultAssistant,
topics: [getDefaultTopic()] topics: [getDefaultTopic()]
}, },
updateDefaultAssistant: (assistant: Assistant) => dispatch(_updateDefaultAssistant({ assistant })) updateDefaultAssistant: (assistant: Assistant) => dispatch(updateDefaultAssistant({ assistant }))
} }
} }

View File

@@ -1,11 +1,11 @@
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { import {
addModel as _addModel, addModel,
removeModel as _removeModel,
updateProvider as _updateProvider,
updateProviders as _updateProviders,
addProvider, addProvider,
removeProvider removeModel,
removeProvider,
updateProvider,
updateProviders
} from '@renderer/store/llm' } from '@renderer/store/llm'
import { Assistant, Model, Provider } from '@renderer/types' import { Assistant, Model, Provider } from '@renderer/types'
import { useDefaultModel } from './useAssistant' import { useDefaultModel } from './useAssistant'
@@ -24,8 +24,8 @@ export function useProviders() {
providers, providers,
addProvider: (provider: Provider) => dispatch(addProvider(provider)), addProvider: (provider: Provider) => dispatch(addProvider(provider)),
removeProvider: (provider: Provider) => dispatch(removeProvider(provider)), removeProvider: (provider: Provider) => dispatch(removeProvider(provider)),
updateProvider: (provider: Provider) => dispatch(_updateProvider(provider)), updateProvider: (provider: Provider) => dispatch(updateProvider(provider)),
updateProviders: (providers: Provider[]) => dispatch(_updateProviders(providers)) updateProviders: (providers: Provider[]) => dispatch(updateProviders(providers))
} }
} }
@@ -48,9 +48,9 @@ export function useProvider(id: string) {
return { return {
provider, provider,
models: provider.models, models: provider.models,
updateProvider: (provider: Provider) => dispatch(_updateProvider(provider)), updateProvider: (provider: Provider) => dispatch(updateProvider(provider)),
addModel: (model: Model) => dispatch(_addModel({ providerId: id, model })), addModel: (model: Model) => dispatch(addModel({ providerId: id, model })),
removeModel: (model: Model) => dispatch(_removeModel({ providerId: id, model })) removeModel: (model: Model) => dispatch(removeModel({ providerId: id, model }))
} }
} }

View File

@@ -41,7 +41,8 @@ const resources = {
'error.enter.model': 'Please select a model first', 'error.enter.model': 'Please select a model first',
'api.connection.failed': 'Connection failed', 'api.connection.failed': 'Connection failed',
'api.connection.success': 'Connection successful', 'api.connection.success': 'Connection successful',
'chat.completion.paused': 'Chat completion paused' 'chat.completion.paused': 'Chat completion paused',
'switch.disabled': 'Switching is disabled while the assistant is generating'
}, },
assistant: { assistant: {
'default.name': '😀 Default Assistant', 'default.name': '😀 Default Assistant',
@@ -66,7 +67,7 @@ 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: ', '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.',
@@ -96,11 +97,11 @@ const resources = {
}, },
settings: { settings: {
title: 'Settings', title: 'Settings',
general: 'General', general: 'General Settings',
provider: 'Model Provider', provider: 'Model Provider',
model: 'Model Settings', model: 'Model Settings',
assistant: 'Default Assistant', assistant: 'Default Assistant',
about: 'About', about: 'About & Feedback',
'general.title': 'General Settings', 'general.title': 'General Settings',
'provider.api_key': 'API Key', 'provider.api_key': 'API Key',
'provider.check': 'Check', 'provider.check': 'Check',
@@ -132,7 +133,16 @@ const resources = {
'provider.delete.title': 'Delete Provider', 'provider.delete.title': 'Delete Provider',
'provider.delete.content': 'Are you sure you want to delete this provider?', 'provider.delete.content': 'Are you sure you want to delete this provider?',
'provider.edit.name': 'Provider Name', 'provider.edit.name': 'Provider Name',
'provider.edit.name.placeholder': 'Example: OpenAI' 'provider.edit.name.placeholder': 'Example: OpenAI',
'about.title': 'About',
'about.releases.title': '📔 Release Notes',
'about.releases.button': 'Releases',
'about.website.title': '🌐 Official Website',
'about.website.button': 'Website',
'about.feedback.title': '📝 Feedback',
'about.feedback.button': 'Feedback',
'about.contact.title': '📧 Contact',
'about.contact.button': 'Email'
} }
} }
}, },
@@ -174,7 +184,8 @@ const resources = {
'error.enter.model': '请选择一个模型', 'error.enter.model': '请选择一个模型',
'api.connection.failed': '连接失败', 'api.connection.failed': '连接失败',
'api.connection.success': '连接成功', 'api.connection.success': '连接成功',
'chat.completion.paused': '会话已停止' 'chat.completion.paused': '会话已停止',
'switch.disabled': '模型回复完成后才能切换'
}, },
assistant: { assistant: {
'default.name': '😃 默认助手 - Assistant', 'default.name': '😃 默认助手 - Assistant',
@@ -234,7 +245,7 @@ const resources = {
provider: '模型提供商', provider: '模型提供商',
model: '模型设置', model: '模型设置',
assistant: '默认助手', assistant: '默认助手',
about: '关于', about: '关于我们',
'general.title': '常规设置', 'general.title': '常规设置',
'provider.api_key': 'API 密钥', 'provider.api_key': 'API 密钥',
'provider.check': '检查', 'provider.check': '检查',
@@ -257,7 +268,7 @@ const resources = {
'models.empty': '没有模型', 'models.empty': '没有模型',
'assistant.title': '默认助手', 'assistant.title': '默认助手',
'assistant.model_params': '模型参数', 'assistant.model_params': '模型参数',
'about.description': '一为创造者而生的 AI 助手', 'about.description': '一为创造者而生的 AI 助手',
'about.updateNotAvailable': '你的软件已是最新版本', 'about.updateNotAvailable': '你的软件已是最新版本',
'about.checkingUpdate': '正在检查更新...', 'about.checkingUpdate': '正在检查更新...',
'about.updateError': '更新出错', 'about.updateError': '更新出错',
@@ -266,7 +277,16 @@ const resources = {
'provider.delete.title': '删除提供商', 'provider.delete.title': '删除提供商',
'provider.delete.content': '确定要删除此模型提供商吗?', 'provider.delete.content': '确定要删除此模型提供商吗?',
'provider.edit.name': '模型提供商名称', 'provider.edit.name': '模型提供商名称',
'provider.edit.name.placeholder': '例如 OpenAI' 'provider.edit.name.placeholder': '例如 OpenAI',
'about.title': '关于我们',
'about.releases.title': '📔 更新日志',
'about.releases.button': '查看',
'about.website.title': '🌐 官方网站',
'about.website.button': '查看',
'about.feedback.title': '📝 意见反馈',
'about.feedback.button': '反馈',
'about.contact.title': '📧 邮件联系',
'about.contact.button': '邮件'
} }
} }
} }

View File

@@ -1,6 +1,6 @@
import { QuestionCircleOutlined } from '@ant-design/icons' import { QuestionCircleOutlined } from '@ant-design/icons'
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant' import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
import { useAssistants } from '@renderer/hooks/useAssistant' import { useAssistant } 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 { debounce } from 'lodash' import { debounce } from 'lodash'
@@ -12,26 +12,26 @@ interface Props {
assistant: Assistant assistant: Assistant
} }
const PopoverContent: FC<Props> = ({ assistant }) => { const PopoverContent: FC<Props> = (props) => {
const { updateAssistant } = useAssistants() const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id)
const [temperature, setTemperature] = useState(assistant.settings?.temperature ?? DEFAULT_TEMPERATURE) const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
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 = useCallback( const onUpdateAssistantSettings = useCallback(
debounce( debounce(
({ _temperature, _contextCount }: { _temperature?: number; _contextCount?: number }) => { ({ _temperature, _contextCount }: { _temperature?: number; _contextCount?: number }) => {
updateAssistant({ updateAssistantSettings({
...assistant,
settings: {
...assistant.settings, ...assistant.settings,
temperature: _temperature ?? temperature, temperature: _temperature ?? temperature,
contextCount: _contextCount ?? contextCount contextCount: _contextCount ?? contextCount
}
}) })
}, },
1000, 1000,
{ leading: false, trailing: true } {
leading: false,
trailing: true
}
), ),
[] []
) )
@@ -64,8 +64,8 @@ const PopoverContent: FC<Props> = ({ assistant }) => {
} }
useEffect(() => { useEffect(() => {
setTemperature(assistant.settings?.temperature ?? DEFAULT_TEMPERATURE) setTemperature(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
setConextCount(assistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT) setConextCount(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
}, [assistant]) }, [assistant])
return ( return (

View File

@@ -1,13 +1,15 @@
import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons' import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd' import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup' import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
import { useAssistants } from '@renderer/hooks/useAssistant' import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
import { getDefaultTopic } from '@renderer/services/assistant' import { getDefaultTopic } from '@renderer/services/assistant'
import { useAppSelector } from '@renderer/store'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { droppableReorder, uuid } from '@renderer/utils' import { droppableReorder, uuid } from '@renderer/utils'
import { Dropdown, MenuProps } from 'antd' import { Dropdown } from 'antd'
import { ItemType } from 'antd/es/menu/interface'
import { last } from 'lodash' import { last } from 'lodash'
import { FC, useRef } from 'react' import { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@@ -18,8 +20,9 @@ interface Props {
} }
const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAssistant }) => { const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAssistant }) => {
const { assistants, removeAssistant, updateAssistant, addAssistant, updateAssistants } = useAssistants() const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
const targetAssistant = useRef<Assistant | null>(null) const { updateAssistant } = useAssistant(activeAssistant.id)
const generating = useAppSelector((state) => state.runtime.generating)
const { t } = useTranslation() const { t } = useTranslation()
@@ -29,26 +32,25 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
removeAssistant(assistant.id) removeAssistant(assistant.id)
} }
const items: MenuProps['items'] = [ const getMenuItems = (assistant: Assistant) =>
[
{ {
label: t('common.edit'), label: t('common.edit'),
key: 'edit', key: 'edit',
icon: <EditOutlined />, icon: <EditOutlined />,
async onClick() { async onClick() {
if (targetAssistant.current) { const _assistant = await AssistantSettingPopup.show({ assistant })
const _assistant = await AssistantSettingPopup.show({ assistant: targetAssistant.current })
updateAssistant(_assistant) updateAssistant(_assistant)
} }
}
}, },
{ {
label: t('common.duplicate'), label: t('common.duplicate'),
key: 'duplicate', key: 'duplicate',
icon: <CopyOutlined />, icon: <CopyOutlined />,
async onClick() { onClick: async () => {
const assistant: Assistant = { ...activeAssistant, id: uuid(), topics: [getDefaultTopic()] } const _assistant: Assistant = { ...assistant, id: uuid(), topics: [getDefaultTopic()] }
addAssistant(assistant) addAssistant(_assistant)
setActiveAssistant(assistant) setActiveAssistant(_assistant)
} }
}, },
{ type: 'divider' }, { type: 'divider' },
@@ -57,11 +59,9 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
key: 'delete', key: 'delete',
icon: <DeleteOutlined />, icon: <DeleteOutlined />,
danger: true, danger: true,
onClick: () => { onClick: () => onDelete(assistant)
targetAssistant.current && onDelete(targetAssistant.current)
} }
} ] as ItemType[]
]
const onDragEnd = (result: DropResult) => { const onDragEnd = (result: DropResult) => {
if (result.destination) { if (result.destination) {
@@ -72,6 +72,14 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
} }
} }
const onSwitchAssistant = (assistant: Assistant) => {
if (generating) {
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
return
}
setActiveAssistant(assistant)
}
return ( return (
<Container> <Container>
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
@@ -82,13 +90,9 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
<Draggable key={`draggable_${assistant.id}_${index}`} draggableId={assistant.id} index={index}> <Draggable key={`draggable_${assistant.id}_${index}`} draggableId={assistant.id} index={index}>
{(provided) => ( {(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<Dropdown <Dropdown key={assistant.id} menu={{ items: getMenuItems(assistant) }} trigger={['contextMenu']}>
key={assistant.id}
menu={{ items }}
trigger={['contextMenu']}
onOpenChange={() => (targetAssistant.current = assistant)}>
<AssistantItem <AssistantItem
onClick={() => setActiveAssistant(assistant)} onClick={() => onSwitchAssistant(assistant)}
className={assistant.id === activeAssistant?.id ? 'active' : ''}> className={assistant.id === activeAssistant?.id ? 'active' : ''}>
<AssistantName>{assistant.name}</AssistantName> <AssistantName>{assistant.name}</AssistantName>
</AssistantItem> </AssistantItem>

View File

@@ -1,31 +1,32 @@
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { Assistant, Message, Topic } from '@renderer/types'
import { estimateInputTokenCount, uuid } from '@renderer/utils'
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components'
import { MoreOutlined } from '@ant-design/icons'
import { Button, Popconfirm, Tooltip } from 'antd'
import { useShowRightSidebar } from '@renderer/hooks/useStore'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { import {
ClearOutlined, ClearOutlined,
ControlOutlined, ControlOutlined,
FullscreenExitOutlined, FullscreenExitOutlined,
FullscreenOutlined, FullscreenOutlined,
HistoryOutlined, HistoryOutlined,
MoreOutlined,
PauseCircleOutlined, PauseCircleOutlined,
PlusCircleOutlined PlusCircleOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea' import { useAssistant } from '@renderer/hooks/useAssistant'
import { debounce, isEmpty } from 'lodash'
import SendMessageSetting from './SendMessageSetting'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import dayjs from 'dayjs' import { useShowRightSidebar } from '@renderer/hooks/useStore'
import store, { useAppSelector } from '@renderer/store'
import { getDefaultTopic } from '@renderer/services/assistant' import { getDefaultTopic } from '@renderer/services/assistant'
import { useTranslation } from 'react-i18next' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import store, { useAppSelector } from '@renderer/store'
import { setGenerating } from '@renderer/store/runtime' import { setGenerating } from '@renderer/store/runtime'
import { Assistant, Message, Topic } from '@renderer/types'
import { estimateInputTokenCount, uuid } from '@renderer/utils'
import { Button, Popconfirm, Tooltip } from 'antd'
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
import dayjs from 'dayjs'
import { debounce, isEmpty } from 'lodash'
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import AssistantSettings from './AssistantSettings' import AssistantSettings from './AssistantSettings'
import SendMessageSetting from './SendMessageSetting'
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
interface Props { interface Props {
assistant: Assistant assistant: Assistant
@@ -41,7 +42,6 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
const [estimateTokenCount, setEstimateTokenCount] = useState(0) 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)
const { t } = useTranslation() const { t } = useTranslation()
const sendMessage = () => { const sendMessage = () => {
@@ -86,7 +86,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
} }
const addNewTopic = useCallback(() => { const addNewTopic = useCallback(() => {
const topic: Topic = getDefaultTopic() const topic = getDefaultTopic()
addTopic(topic) addTopic(topic)
setActiveTopic(topic) setActiveTopic(topic)
}, [addTopic, setActiveTopic]) }, [addTopic, setActiveTopic])
@@ -194,6 +194,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
styles={{ textarea: { paddingLeft: 0 } }} styles={{ textarea: { paddingLeft: 0 } }}
/> />
<TextCount> <TextCount>
<HistoryOutlined /> {assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT} |{' '}
{t('assistant.input.estimated_tokens')}: {`${inputTokenCount}/${estimateTokenCount}`} {t('assistant.input.estimated_tokens')}: {`${inputTokenCount}/${estimateTokenCount}`}
</TextCount> </TextCount>
</Container> </Container>
@@ -258,6 +259,7 @@ const TextCount = styled.div`
bottom: 8px; bottom: 8px;
font-size: 11px; font-size: 11px;
color: var(--color-text-3); color: var(--color-text-3);
z-index: 10;
` `
export default Inputbar export default Inputbar

View File

@@ -105,7 +105,7 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
{message.usage && ( {message.usage && (
<> <>
<MessageMetadata> <MessageMetadata>
Tokens: {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>
</> </>

View File

@@ -65,7 +65,6 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
useEffect(() => { useEffect(() => {
const unsubscribes = [ const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => { EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => {
console.debug({ assistant, provider, message: msg, topic })
onSendMessage(msg) onSendMessage(msg)
fetchChatCompletion({ assistant, messages: [...messages, msg], topic, onResponse: setLastMessage }) fetchChatCompletion({ assistant, messages: [...messages, msg], topic, onResponse: setLastMessage })
}), }),

View File

@@ -4,13 +4,14 @@ import { useShowRightSidebar } from '@renderer/hooks/useStore'
import { fetchMessagesSummary } from '@renderer/services/api' import { fetchMessagesSummary } from '@renderer/services/api'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { Button, Dropdown, MenuProps, Popconfirm } from 'antd' import { Button, Dropdown, MenuProps, Popconfirm } from 'antd'
import { FC, useRef } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/icons' import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/icons'
import LocalStorage from '@renderer/services/storage' import LocalStorage from '@renderer/services/storage'
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd' import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
import { droppableReorder } from '@renderer/utils' import { droppableReorder } from '@renderer/utils'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useAppSelector } from '@renderer/store'
interface Props { interface Props {
assistant: Assistant assistant: Assistant
@@ -18,25 +19,24 @@ interface Props {
setActiveTopic: (topic: Topic) => void setActiveTopic: (topic: Topic) => void
} }
const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => { const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic }) => {
const { showRightSidebar } = useShowRightSidebar() const { showRightSidebar } = useShowRightSidebar()
const { removeTopic, updateTopic, removeAllTopics, updateTopics } = useAssistant(assistant.id) const { assistant, removeTopic, updateTopic, removeAllTopics, updateTopics } = useAssistant(_assistant.id)
const currentTopic = useRef<Topic | null>(null)
const { t } = useTranslation() const { t } = useTranslation()
const generating = useAppSelector((state) => state.runtime.generating)
const topicMenuItems: MenuProps['items'] = [ const getTopicMenuItems = (topic: Topic) => {
const menus: MenuProps['items'] = [
{ {
label: t('assistant.topics.auto_rename'), label: t('assistant.topics.auto_rename'),
key: 'auto-rename', key: 'auto-rename',
icon: <SignatureOutlined />, icon: <SignatureOutlined />,
async onClick() { async onClick() {
if (currentTopic.current) { const messages = await LocalStorage.getTopicMessages(topic.id)
const messages = await LocalStorage.getTopicMessages(currentTopic.current.id)
if (messages.length >= 2) { if (messages.length >= 2) {
const summaryText = await fetchMessagesSummary({ messages, assistant }) const summaryText = await fetchMessagesSummary({ messages, assistant })
if (summaryText) { if (summaryText) {
updateTopic({ ...currentTopic.current, name: summaryText }) updateTopic({ ...topic, name: summaryText })
}
} }
} }
} }
@@ -49,31 +49,33 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
const name = await PromptPopup.show({ const name = await PromptPopup.show({
title: t('assistant.topics.edit.title'), title: t('assistant.topics.edit.title'),
message: t('assistant.topics.edit.placeholder'), message: t('assistant.topics.edit.placeholder'),
defaultValue: currentTopic.current?.name || '' defaultValue: topic?.name || ''
}) })
if (name && currentTopic.current && currentTopic.current?.name !== name) { if (name && topic?.name !== name) {
updateTopic({ ...currentTopic.current, name }) updateTopic({ ...topic, name })
} }
} }
} }
] ]
if (assistant.topics.length > 1) { if (assistant.topics.length > 1) {
topicMenuItems.push({ type: 'divider' }) menus.push({ type: 'divider' })
topicMenuItems.push({ menus.push({
label: t('common.delete'), label: t('common.delete'),
danger: true, danger: true,
key: 'delete', key: 'delete',
icon: <DeleteOutlined />, icon: <DeleteOutlined />,
onClick() { onClick() {
if (assistant.topics.length === 1) return if (assistant.topics.length === 1) return
currentTopic.current && removeTopic(currentTopic.current) removeTopic(topic)
currentTopic.current = null
setActiveTopic(assistant.topics[0]) setActiveTopic(assistant.topics[0])
} }
}) })
} }
return menus
}
const onDragEnd = (result: DropResult) => { const onDragEnd = (result: DropResult) => {
if (result.destination) { if (result.destination) {
const sourceIndex = result.source.index const sourceIndex = result.source.index
@@ -82,6 +84,14 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
} }
} }
const onSwitchTopic = (topic: Topic) => {
if (generating) {
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
return
}
setActiveTopic(topic)
}
return ( return (
<Container style={{ display: showRightSidebar ? 'block' : 'none' }}> <Container style={{ display: showRightSidebar ? 'block' : 'none' }}>
<TopicTitle> <TopicTitle>
@@ -108,14 +118,10 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
<Draggable key={`draggable_${topic.id}_${index}`} draggableId={topic.id} index={index}> <Draggable key={`draggable_${topic.id}_${index}`} draggableId={topic.id} index={index}>
{(provided) => ( {(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<Dropdown <Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
menu={{ items: topicMenuItems }}
trigger={['contextMenu']}
key={topic.id}
onOpenChange={(open) => open && (currentTopic.current = topic)}>
<TopicListItem <TopicListItem
className={topic.id === activeTopic?.id ? 'active' : ''} className={topic.id === activeTopic?.id ? 'active' : ''}
onClick={() => setActiveTopic(topic)}> onClick={() => onSwitchTopic(topic)}>
{topic.name} {topic.name}
</TopicListItem> </TopicListItem>
</Dropdown> </Dropdown>

View File

@@ -1,12 +1,12 @@
import { Avatar, Button, Progress } from 'antd' import { Avatar, Button, Progress, Row, Tag } from 'antd'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Logo from '@renderer/assets/images/logo.png' import Logo from '@renderer/assets/images/logo.png'
import { runAsyncFunction } from '@renderer/utils' import { runAsyncFunction } from '@renderer/utils'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Changelog from './components/Changelog'
import { debounce } from 'lodash' import { debounce } from 'lodash'
import { ProgressInfo } from 'electron-updater' import { ProgressInfo } from 'electron-updater'
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from './components'
const AboutSettings: FC = () => { const AboutSettings: FC = () => {
const [version, setVersion] = useState('') const [version, setVersion] = useState('')
@@ -26,8 +26,17 @@ const AboutSettings: FC = () => {
{ leading: true, trailing: false } { leading: true, trailing: false }
) )
const onOpenWebsite = (suffix = '') => { const onOpenWebsite = (url: string) => {
window.api.openWebsite('https://github.com/kangfenmao/cherry-studio' + suffix) window.api.openWebsite(url)
}
const mailto = async () => {
const email = 'kangfenmao@qq.com'
const subject = 'Cherry Studio Feedback'
const version = (await window.api.getAppInfo()).version
const platform = window.electron.process.platform
const url = `mailto:${email}?subject=${subject}&body=%0A%0AVersion: ${version} | Platform: ${platform}`
onOpenWebsite(url)
} }
useEffect(() => { useEffect(() => {
@@ -69,57 +78,92 @@ const AboutSettings: FC = () => {
}, [t]) }, [t])
return ( return (
<Container> <SettingContainer>
<AvatarWrapper onClick={() => onOpenWebsite()}> <SettingTitle>{t('settings.about.title')}</SettingTitle>
<SettingDivider />
<AboutHeader>
<Row align="middle">
<AvatarWrapper onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio')}>
{percent > 0 && ( {percent > 0 && (
<ProgressCircle <ProgressCircle
type="circle" type="circle"
size={104} size={84}
percent={percent} percent={percent}
showInfo={false} showInfo={false}
strokeLinecap="butt" strokeLinecap="butt"
strokeColor="#67ad5b" strokeColor="#67ad5b"
/> />
)} )}
<Avatar src={Logo} size={100} style={{ marginTop: 50, minHeight: 100 }} /> <Avatar src={Logo} size={80} style={{ minHeight: 80 }} />
</AvatarWrapper> </AvatarWrapper>
<Title> <VersionWrapper>
Cherry Studio <Version onClick={() => onOpenWebsite('/releases')}>(v{version})</Version> <Title>Cherry Studio</Title>
</Title>
<Description>{t('settings.about.description')}</Description> <Description>{t('settings.about.description')}</Description>
<Tag
onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio/releases')}
color="cyan"
style={{ marginTop: 8, cursor: 'pointer' }}>
v{version}
</Tag>
</VersionWrapper>
</Row>
<CheckUpdateButton onClick={onCheckUpdate} loading={checkUpdateLoading}> <CheckUpdateButton onClick={onCheckUpdate} loading={checkUpdateLoading}>
{downloading ? t('settings.about.downloading') : t('settings.about.checkUpdate')} {downloading ? t('settings.about.downloading') : t('settings.about.checkUpdate')}
</CheckUpdateButton> </CheckUpdateButton>
<Changelog /> </AboutHeader>
</Container> <SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.about.releases.title')}</SettingRowTitle>
<Button onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio/releases')}>
{t('settings.about.releases.button')}
</Button>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.about.website.title')}</SettingRowTitle>
<Button onClick={() => onOpenWebsite('https://easys.run/cherry-studio')}>
{t('settings.about.website.button')}
</Button>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.about.feedback.title')}</SettingRowTitle>
<Button onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio/issues')}>
{t('settings.about.feedback.button')}
</Button>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.about.contact.title')}</SettingRowTitle>
<Button onClick={mailto}>{t('settings.about.contact.button')}</Button>
</SettingRow>
<SettingDivider />
</SettingContainer>
) )
} }
const Container = styled.div` const AboutHeader = styled.div`
display: flex; display: flex;
flex: 1; flex-direction: row;
flex-direction: column;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: space-between;
height: calc(100vh - var(--navbar-height)); width: 100%;
overflow-y: scroll; padding: 5px 0;
padding: 0; `
padding-bottom: 50px;
const VersionWrapper = styled.div`
display: flex;
flex-direction: column;
min-height: 80px;
justify-content: center;
align-items: flex-start;
` `
const Title = styled.div` const Title = styled.div`
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
color: var(--color-text-1); color: var(--color-text-1);
margin: 10px 0; margin-bottom: 5px;
`
const Version = styled.span`
font-size: 14px;
color: var(--color-text-2);
margin: 10px 0;
text-align: center;
cursor: pointer;
` `
const Description = styled.div` const Description = styled.div`
@@ -128,18 +172,17 @@ const Description = styled.div`
text-align: center; text-align: center;
` `
const CheckUpdateButton = styled(Button)` const CheckUpdateButton = styled(Button)``
margin-top: 10px;
`
const AvatarWrapper = styled.div` const AvatarWrapper = styled.div`
position: relative; position: relative;
cursor: pointer; cursor: pointer;
margin-right: 15px;
` `
const ProgressCircle = styled(Progress)` const ProgressCircle = styled(Progress)`
position: absolute; position: absolute;
top: 48px; top: -2px;
left: -2px; left: -2px;
` `

View File

@@ -1,29 +0,0 @@
import changelogEn from '@renderer/CHANGELOG.en.md?raw'
import changelogZh from '@renderer/CHANGELOG.zh.md?raw'
import { FC } from 'react'
import Markdown from 'react-markdown'
import styled from 'styled-components'
import styles from './changelog.module.scss'
import i18n from '@renderer/i18n'
const Changelog: FC = () => {
const language = i18n.language
const changelog = language === 'zh-CN' ? changelogZh : changelogEn
return (
<Container>
<Markdown className={styles.markdown}>{changelog}</Markdown>
</Container>
)
}
const Container = styled.div`
font-size: 14px;
background-color: var(--color-background-soft);
margin-top: 40px;
padding: 20px;
border-radius: 5px;
width: 650px;
`
export default Changelog

View File

@@ -1,79 +0,0 @@
$background-color: #121212;
$text-color: #ffffff;
$heading-color: #00b96b;
$link-color: #3498db;
$code-background: #1e1e1e;
$code-color: #f0e7db;
.markdown {
body {
background-color: $background-color;
color: $text-color;
font-family: Arial, sans-serif;
padding: 20px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: $heading-color;
}
h1 {
font-size: 22px;
font-weight: 700;
}
h3 {
margin: 10px 0;
font-weight: 500;
font-family: Arial, sans-serif;
}
a {
color: $link-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
strong {
font-weight: bold;
}
pre {
background-color: $code-background;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
}
code {
background-color: $code-background;
color: $code-color;
padding: 2px 4px;
border-radius: 3px;
}
blockquote {
border-left: 4px solid $heading-color;
padding-left: 10px;
margin-left: 0;
color: #b3b3b3;
}
ul,
ol {
padding-left: 20px;
list-style: disc;
}
li {
margin-bottom: 5px;
}
}

View File

@@ -47,7 +47,7 @@ export default class ProviderSDK {
model: model.id, model: model.id,
messages: [systemMessage, ...userMessages].filter(Boolean) as MessageParam[], messages: [systemMessage, ...userMessages].filter(Boolean) as MessageParam[],
max_tokens: 4096, max_tokens: 4096,
temperature: assistant.settings?.temperature temperature: assistant?.settings?.temperature
}) })
.on('text', (text) => onChunk({ text: text || '' })) .on('text', (text) => onChunk({ text: text || '' }))
.on('finalMessage', (message) => .on('finalMessage', (message) =>
@@ -64,7 +64,7 @@ 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,
temperature: assistant.settings?.temperature temperature: assistant?.settings?.temperature
}) })
for await (const chunk of stream) { for await (const chunk of stream) {
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) break if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) break
@@ -83,7 +83,7 @@ export default class ProviderSDK {
const systemMessage = { const systemMessage = {
role: 'system', role: 'system',
content: '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要标点符号' content: '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
} }
if (this.isAnthropic) { if (this.isAnthropic) {

View File

@@ -1,7 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { getDefaultAssistant, getDefaultTopic } from '@renderer/services/assistant' import { getDefaultAssistant, getDefaultTopic } from '@renderer/services/assistant'
import LocalStorage from '@renderer/services/storage' import LocalStorage from '@renderer/services/storage'
import { Assistant, Model, Topic } from '@renderer/types' import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
import { uniqBy } from 'lodash' import { uniqBy } from 'lodash'
export interface AssistantsState { export interface AssistantsState {
@@ -33,6 +33,16 @@ const assistantsSlice = createSlice({
updateAssistant: (state, action: PayloadAction<Assistant>) => { updateAssistant: (state, action: PayloadAction<Assistant>) => {
state.assistants = state.assistants.map((c) => (c.id === action.payload.id ? action.payload : c)) state.assistants = state.assistants.map((c) => (c.id === action.payload.id ? action.payload : c))
}, },
updateAssistantSettings: (state, action: PayloadAction<{ assistantId: string; settings: AssistantSettings }>) => {
state.assistants = state.assistants.map((assistant) =>
assistant.id === action.payload.assistantId
? {
...assistant,
settings: action.payload.settings
}
: assistant
)
},
addTopic: (state, action: PayloadAction<{ assistantId: string; topic: Topic }>) => { addTopic: (state, action: PayloadAction<{ assistantId: string; topic: Topic }>) => {
state.assistants = state.assistants.map((assistant) => state.assistants = state.assistants.map((assistant) =>
assistant.id === action.payload.assistantId assistant.id === action.payload.assistantId
@@ -111,7 +121,8 @@ export const {
updateTopic, updateTopic,
updateTopics, updateTopics,
removeAllTopics, removeAllTopics,
setModel setModel,
updateAssistantSettings
} = assistantsSlice.actions } = assistantsSlice.actions
export default assistantsSlice.reducer export default assistantsSlice.reducer

View File

@@ -169,10 +169,10 @@ export function getFirstCharacter(str) {
} }
export const getAssistantSettings = (assistant: Assistant): AssistantSettings => { export const getAssistantSettings = (assistant: Assistant): AssistantSettings => {
const contextCount = assistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT const contextCount = assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT
return { return {
contextCount: contextCount === 20 ? 100000 : contextCount, contextCount: contextCount === 20 ? 100000 : contextCount,
temperature: assistant.settings?.temperature ?? DEFAULT_TEMPERATURE temperature: assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE
} }
} }