Compare commits

...

22 Commits

Author SHA1 Message Date
kangfenmao
6b872f58ed 0.2.3 2024-07-16 11:02:53 +08:00
kangfenmao
d5da7e4413 docs: update change log 2024-07-16 11:02:37 +08:00
kangfenmao
3e88aa3c36 fix(i18n): provider name en 2024-07-16 10:52:23 +08:00
kangfenmao
d6fc1cb364 fix: zhipu provider default model data error 2024-07-16 10:40:28 +08:00
亢奋猫
224f23aea0 Update README.md 2024-07-16 10:22:24 +08:00
亢奋猫
8c186b757e Update README.md 2024-07-16 10:21:12 +08:00
kangfenmao
3f32775b98 fix: provider model edit popup title 2024-07-15 18:07:04 +08:00
kangfenmao
067819652b fix(llm): zhipu provider default models is wrong 2024-07-15 18:02:41 +08:00
kangfenmao
b3a023e4ac fix: model checking error 2024-07-15 17:58:58 +08:00
kangfenmao
1da1b6622d 0.2.2 2024-07-15 17:32:04 +08:00
kangfenmao
78e1626e52 feat: update change log styles 2024-07-15 17:31:39 +08:00
kangfenmao
ec49cf61d6 fix: default assistant name empty 2024-07-15 17:22:51 +08:00
kangfenmao
b487c68822 0.2.1 2024-07-15 16:45:01 +08:00
kangfenmao
74aa95339c feat: add changes log 2024-07-15 15:55:51 +08:00
kangfenmao
e90ef9d05f feat: add release note 2024-07-15 15:00:19 +08:00
kangfenmao
e8bdf9d5fd feat: add pause icon to pause chat completion 2024-07-15 14:21:36 +08:00
kangfenmao
a97d6f024b fix(i18n): reload after change language 2024-07-15 14:16:43 +08:00
kangfenmao
b4eb35d86a feat: add keyv-storage 2024-07-15 13:13:42 +08:00
kangfenmao
3fc45187eb ci: remove any yml files from the release files 2024-07-15 12:39:03 +08:00
kangfenmao
f1c5a41fff build: fix artifactName with arch 2024-07-15 11:49:55 +08:00
kangfenmao
5a7f943448 ci(release): remove blockmap files 2024-07-15 11:49:33 +08:00
kangfenmao
b02e83f68e feat: add x64 arch to mac dmg target 2024-07-15 11:25:32 +08:00
34 changed files with 347 additions and 66 deletions

View File

@@ -2,3 +2,4 @@ node_modules
dist
out
.gitignore

View File

@@ -70,7 +70,6 @@ jobs:
dist/*.deb
dist/*.rpm
dist/*.tar.gz
dist/*.yml
dist/*.blockmap
dist/latest*.yml
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

View File

@@ -4,3 +4,4 @@ pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
CHANGELOG*.md

View File

@@ -2,6 +2,22 @@
Cherry Studio is a desktop client for multiple cutting-edge LLM models, available on Windows, Mac and Linux.
# Screenshot
![image.png](https://s2.loli.net/2024/07/16/IAVSOorsfFQyGhM.png)
![image.png](https://s2.loli.net/2024/07/16/IQPz12OajfNoBTV.png)
# Feature
1. Supports multiple large language model service providers.
2. Allows creation of multiple Assistants.
3. Enables creation of multiple topics.
4. Allows using multiple models to answer questions in the same conversation.
5. Supports drag-and-drop sorting.
6. Code highlighting.
# Develop
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)

View File

@@ -26,8 +26,17 @@ mac:
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false
target:
- target: dmg
arch:
- arm64
- x64
- target: zip
arch:
- arm64
- x64
dmg:
artifactName: ${productName}-${version}.${ext}
artifactName: ${productName}-${version}-${arch}.${ext}
linux:
target:
- AppImage
@@ -45,3 +54,9 @@ publish:
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/
afterSign: scripts/notarize.js
releaseInfo:
releaseNotes: |
- 修复多语言提示错误
- 修复智谱AI默认模型错误问题
- 修复 OpenRouter API 检测出错问题
- 修复模型提供商多语言翻译错误问题

View File

@@ -16,6 +16,7 @@ export default defineConfig({
}
},
plugins: [react()],
assetsInclude: ['**/*.md'],
server: {
host: '0.0.0.0'
}

View File

@@ -1,6 +1,6 @@
{
"name": "cherry-studio",
"version": "0.2.0",
"version": "0.2.3",
"description": "A powerful AI assistant for producer.",
"main": "./out/main/index.js",
"author": "kangfenmao@qq.com",
@@ -33,6 +33,7 @@
"@electron-toolkit/tsconfig": "^1.0.1",
"@fontsource/inter": "^5.0.18",
"@hello-pangea/dnd": "^16.6.0",
"@kangfenmao/keyv-storage": "^0.1.0",
"@reduxjs/toolkit": "^2.2.5",
"@types/lodash": "^4.17.5",
"@types/node": "^18.19.9",

View File

@@ -94,7 +94,9 @@ app.whenReady().then(() => {
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log('An error occurred: ', err))
setTimeout(() => new AppUpdater(), 5000)
if (app.isPackaged) {
setTimeout(() => new AppUpdater(), 3000)
}
})
// Quit when all windows are closed, except on macOS. There, it's common

View File

@@ -8,12 +8,12 @@ export default class AppUpdater {
autoUpdater.logger = logger
autoUpdater.forceDevUpdateConfig = true
autoUpdater.autoDownload = false
autoUpdater.checkForUpdatesAndNotify()
autoUpdater.checkForUpdates()
// 触发检查更新(此方法用于被渲染线程调用,例如页面点击检查更新按钮来调用此方法)
ipcMain.on('check-for-update', () => {
logger.info('触发检查更新')
autoUpdater.checkForUpdates()
return autoUpdater.checkForUpdates()
})
// 检测下载错误

View File

@@ -3,7 +3,8 @@ import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const api = {
getAppInfo: () => ipcRenderer.invoke('get-app-info')
getAppInfo: () => ipcRenderer.invoke('get-app-info'),
checkForUpdate: () => ipcRenderer.invoke('check-for-update')
}
// Use `contextBridge` APIs to expose Electron APIs to

View File

@@ -7,7 +7,6 @@ import { PersistGate } from 'redux-persist/integration/react'
import Sidebar from './components/app/Sidebar'
import TopViewContainer from './components/TopView'
import { AntdThemeConfig, getAntdLocale } from './config/antd'
import './i18n'
import AppsPage from './pages/apps/AppsPage'
import HomePage from './pages/home/HomePage'
import SettingsPage from './pages/settings/SettingsPage'

View File

@@ -0,0 +1,20 @@
# CHANGES LOG
### v0.2.3 - 2024-07-16
1. Fixed multi-language prompt errors
2. Fixed default model error issues with ZHIPU AI
3. Fixed OpenRouter API detection error issues
4. Fixed multi-language translation errors with model providers
### v0.2.2 - 2024-07-15
1. Fix the issue where the default assistant name is empty.
2. Fix the problem with default language detection during the first installation.
3. Adjust the changelog style.
### v0.2.1 - 2024-07-15
1. **Feature**: Add new feature for pausing message sending
2. **Fix**: Resolve incomplete translation issue upon language switch
3. **Build**: Support for macOS Intel architecture

View File

@@ -0,0 +1,21 @@
# 更新日志
### v0.2.3 - 2024-07-16
1. 修复多语言提示错误
2. 修复智谱AI默认模型错误问题
3. 修复 OpenRouter API 检测出错问题
4. 修复模型提供商多语言翻译错误问题
### v0.2.2 - 2024-07-15
1. 修复默认助理名称为空的问题
2. 修复首次安装默认语言检测问题
3. 更新日志样式微调
### v0.2.1 - 2024-07-15
1. 【功能】新增消息暂停发送功能
2. 【修复】修复多语言切换不彻底问题
3. 【构建】支持 macOS Intel 架构

View File

@@ -0,0 +1,78 @@
$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: 30px;
}
li {
margin-bottom: 5px;
}
}

View File

@@ -2,10 +2,12 @@
import { MessageInstance } from 'antd/es/message/interface'
import { HookAPI } from 'antd/es/modal/useModal'
import type KeyvStorage from '@kangfenmao/keyv-storage'
declare global {
interface Window {
message: MessageInstance
modal: HookAPI
keyv: KeyvStorage
}
}

View File

@@ -1,3 +1,4 @@
import { i18nInit } from '@renderer/i18n'
import LocalStorage from '@renderer/services/storage'
import { useAppDispatch } from '@renderer/store'
import { setAvatar } from '@renderer/store/runtime'
@@ -12,5 +13,6 @@ export function useAppInitEffect() {
const storedImage = await LocalStorage.getImage('avatar')
storedImage && dispatch(setAvatar(storedImage))
})
i18nInit()
}, [dispatch])
}

View File

@@ -39,7 +39,8 @@ const resources = {
'error.enter.api.host': 'Please enter your API host first',
'error.enter.model': 'Please select a model first',
'api.connection.failed': 'Connection failed',
'api.connection.success': 'Connection successful'
'api.connection.success': 'Connection successful',
'chat.completion.paused': 'Chat completion paused'
},
assistant: {
'default.name': 'Default Assistant',
@@ -61,7 +62,8 @@ const resources = {
'input.clear.title': 'Clear all messages?',
'input.clear.content': 'Are you sure to clear all messages?',
'input.placeholder': 'Type your message here...',
'input.send': 'Send'
'input.send': 'Send',
'input.pause': 'Pause'
},
apps: {
title: 'Agents'
@@ -72,8 +74,8 @@ const resources = {
moonshot: 'Moonshot',
silicon: 'SiliconFlow',
openrouter: 'OpenRouter',
yi: 'Lingyiwanwu',
zhipu: 'BigModel',
yi: 'Yi',
zhipu: 'ZHIPU AI',
groq: 'Groq',
ollama: 'Ollama'
},
@@ -146,7 +148,8 @@ const resources = {
'error.enter.api.host': '请输入您的 API 地址',
'error.enter.model': '请选择一个模型',
'api.connection.failed': '连接失败',
'api.connection.successful': '连接成功'
'api.connection.successful': '连接成功',
'chat.completion.paused': '会话已停止'
},
assistant: {
'default.name': '默认助手',
@@ -168,7 +171,8 @@ const resources = {
'input.clear.title': '清除所有消息?',
'input.clear.content': '确定要清除所有消息吗?',
'input.placeholder': '在这里输入消息...',
'input.send': '发送'
'input.send': '发送',
'input.pause': '暂停'
},
apps: {
title: '智能体'
@@ -221,11 +225,15 @@ const resources = {
i18n.use(initReactI18next).init({
resources,
lng: store.getState().settings.language || 'en-US',
lng: localStorage.getItem('language') || navigator.language || 'en-US',
fallbackLng: 'en-US',
interpolation: {
escapeValue: false
}
})
export function i18nInit() {
i18n.changeLanguage(store.getState().settings.language || 'en-US')
}
export default i18n

View File

@@ -1,4 +1,5 @@
import localforage from 'localforage'
import KeyvStorage from '@kangfenmao/keyv-storage'
function init() {
localforage.config({
@@ -8,6 +9,8 @@ function init() {
storeName: 'cherryai',
description: 'Cherry Studio Storage'
})
window.keyv = new KeyvStorage()
window.keyv.init()
}
init()

View File

@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'
import App from './App'
import './assets/styles/index.scss'
import './init'
import './i18n'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>

View File

@@ -12,6 +12,7 @@ import {
FullscreenExitOutlined,
FullscreenOutlined,
HistoryOutlined,
PauseCircleOutlined,
PlusCircleOutlined
} from '@ant-design/icons'
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
@@ -19,9 +20,10 @@ import { isEmpty } from 'lodash'
import SendMessageSetting from './SendMessageSetting'
import { useSettings } from '@renderer/hooks/useSettings'
import dayjs from 'dayjs'
import { useAppSelector } from '@renderer/store'
import store, { useAppSelector } from '@renderer/store'
import { getDefaultTopic } from '@renderer/services/assistant'
import { useTranslation } from 'react-i18next'
import { setGenerating } from '@renderer/store/runtime'
interface Props {
assistant: Assistant
@@ -86,6 +88,11 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
const clearTopic = () => EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES)
const onPause = () => {
window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, true)
store.dispatch(setGenerating(false))
}
// Command or Ctrl + N create new topic
useEffect(() => {
const onKeydown = (e) => {
@@ -148,6 +155,13 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
</Tooltip>
</ToolbarMenu>
<ToolbarMenu>
{generating && (
<Tooltip placement="top" title={t('assistant.input.pause')} arrow>
<ToolbarButton type="text" onClick={onPause}>
<PauseCircleOutlined />
</ToolbarButton>
</Tooltip>
)}
<SendMessageSetting>
<ToolbarButton type="text" style={{ marginRight: 0 }}>
<MoreOutlined />

View File

@@ -12,6 +12,7 @@ import Logo from '@renderer/assets/images/logo.png'
import { SyncOutlined } from '@ant-design/icons'
import { firstLetter } from '@renderer/utils'
import { useTranslation } from 'react-i18next'
import { isEmpty } from 'lodash'
interface Props {
message: Message
@@ -53,6 +54,13 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
setTimeout(() => EventEmitter.emit(EVENT_NAMES.REGENERATE_MESSAGE), 100)
}
const getMessageContent = (message: Message) => {
if (isEmpty(message.content) && message.status === 'paused') {
return t('message.chat.completion.paused')
}
return message.content
}
return (
<MessageContainer key={message.id}>
<AvatarWrapper>
@@ -72,7 +80,7 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
)}
{message.status !== 'sending' && (
<Markdown className="markdown" components={{ code: CodeBlock as any }}>
{message.content}
{getMessageContent(message)}
</Markdown>
)}
{showMenu && (

View File

@@ -4,6 +4,7 @@ import styled from 'styled-components'
import Logo from '@renderer/assets/images/logo.png'
import { runAsyncFunction } from '@renderer/utils'
import { useTranslation } from 'react-i18next'
import Changelog from './components/Changelog'
const AboutSettings: FC = () => {
const [version, setVersion] = useState('')
@@ -23,6 +24,7 @@ const AboutSettings: FC = () => {
Cherry Studio <Version>(v{version})</Version>
</Title>
<Description>{t('settings.about.description')}</Description>
<Changelog />
</Container>
)
}

View File

@@ -1,6 +1,6 @@
import { FC } from 'react'
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from './components'
import { Avatar, message, Select, Upload } from 'antd'
import { Avatar, Select, Upload } from 'antd'
import styled from 'styled-components'
import LocalStorage from '@renderer/services/storage'
import { compressImage } from '@renderer/utils'
@@ -14,7 +14,6 @@ import i18next from 'i18next'
const GeneralSettings: FC = () => {
const avatar = useAvatar()
const [messageApi, contextHolder] = message.useMessage()
const { language } = useSettings()
const dispatch = useAppDispatch()
const { t } = useTranslation()
@@ -22,14 +21,26 @@ const GeneralSettings: FC = () => {
const onSelectLanguage = (value: string) => {
dispatch(setLanguage(value))
i18next.changeLanguage(value)
// window.location.reload()
localStorage.setItem('language', value)
}
return (
<SettingContainer>
{contextHolder}
<SettingTitle>{t('settings.general.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('common.language')}</SettingRowTitle>
<Select
defaultValue={language || 'en-US'}
style={{ width: 120 }}
onChange={onSelectLanguage}
options={[
{ value: 'zh-CN', label: '中文' },
{ value: 'en-US', label: 'English' }
]}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('common.avatar')}</SettingRowTitle>
<Upload
@@ -44,29 +55,13 @@ const GeneralSettings: FC = () => {
await LocalStorage.storeImage('avatar', compressedFile)
dispatch(setAvatar(await LocalStorage.getImage('avatar')))
} catch (error: any) {
messageApi.open({
type: 'error',
content: error.message
})
window.message.error(error.message)
}
}}>
<UserAvatar src={avatar} size="large" />
</Upload>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('common.language')}</SettingRowTitle>
<Select
defaultValue={language || 'en-US'}
style={{ width: 120 }}
onChange={onSelectLanguage}
options={[
{ value: 'zh-CN', label: '中文' },
{ value: 'en-US', label: 'English' }
]}
/>
</SettingRow>
<SettingDivider />
</SettingContainer>
)
}

View File

@@ -0,0 +1,29 @@
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 '@renderer/assets/styles/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

@@ -86,7 +86,7 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
return (
<Flex>
<ModelHeaderTitle>
{provider.name} {t('common.models')}
{t(`provider.${provider.id}`)} {t('common.models')}
</ModelHeaderTitle>
{loading && <LoadingOutlined size={20} />}
</Flex>

View File

@@ -18,7 +18,8 @@ interface Props {
provider: Provider
}
const ProviderSetting: FC<Props> = ({ provider }) => {
const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
const { provider } = useProvider(_provider.id)
const [apiKey, setApiKey] = useState(provider.apiKey)
const [apiHost, setApiHost] = useState(provider.apiHost)
const [apiValid, setApiValid] = useState(false)

View File

@@ -8,7 +8,7 @@ import { takeRight } from 'lodash'
import dayjs from 'dayjs'
import store from '@renderer/store'
import { setGenerating } from '@renderer/store/runtime'
import { t } from 'i18next'
import i18n from '@renderer/i18n'
interface FetchChatCompletionParams {
messages: Message[]
@@ -27,6 +27,8 @@ const getOpenAiProvider = (provider: Provider) => {
}
export async function fetchChatCompletion({ messages, topic, assistant, onResponse }: FetchChatCompletionParams) {
window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, false)
const provider = getAssistantProvider(assistant)
const openaiProvider = getOpenAiProvider(provider)
const defaultModel = getDefaultModel()
@@ -34,7 +36,7 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
store.dispatch(setGenerating(true))
const _message: Message = {
const message: Message = {
id: uuid(),
role: 'assistant',
content: '',
@@ -45,7 +47,7 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
status: 'sending'
}
onResponse({ ..._message })
onResponse({ ...message })
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
@@ -54,12 +56,10 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
content: message.content
}))
const _messages = [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[]
try {
const stream = await openaiProvider.chat.completions.create({
model: model.id,
messages: _messages,
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
stream: true
})
@@ -67,22 +67,27 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
let usage: OpenAI.Completions.CompletionUsage | undefined = undefined
for await (const chunk of stream) {
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) {
break
}
content = content + (chunk.choices[0]?.delta?.content || '')
chunk.usage && (usage = chunk.usage)
onResponse({ ..._message, content, status: 'pending' })
onResponse({ ...message, content, status: 'pending' })
}
_message.content = content
_message.usage = usage
message.content = content
message.usage = usage
} catch (error: any) {
_message.content = `Error: ${error.message}`
message.content = `Error: ${error.message}`
}
_message.status = 'success'
EventEmitter.emit(EVENT_NAMES.AI_CHAT_COMPLETION, _message)
const paused = window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)
message.status = paused ? 'paused' : 'success'
EventEmitter.emit(EVENT_NAMES.AI_CHAT_COMPLETION, message)
store.dispatch(setGenerating(false))
return _message
return message
}
interface FetchMessagesSummaryParams {
@@ -122,17 +127,17 @@ export async function checkApi(provider: Provider) {
const style = { marginTop: '3vh' }
if (!provider.apiKey) {
window.message.error({ content: t('error.enter.api.key'), key, style })
window.message.error({ content: i18n.t('message.error.enter.api.key'), key, style })
return false
}
if (!provider.apiHost) {
window.message.error({ content: t('error.enter.api.host'), key, style })
window.message.error({ content: i18n.t('message.error.enter.api.host'), key, style })
return false
}
if (!model) {
window.message.error({ content: t('error.enter.model'), key, style })
window.message.error({ content: i18n.t('message.error.enter.model'), key, style })
return false
}
@@ -142,7 +147,8 @@ export async function checkApi(provider: Provider) {
try {
const response = await openaiProvider.chat.completions.create({
model: model.id,
messages: [{ role: 'user', content: 'hello' }],
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 100,
stream: false
})
@@ -156,7 +162,9 @@ export async function checkApi(provider: Provider) {
key: 'api-check',
style: { marginTop: '3vh' },
duration: valid ? 2 : 8,
content: valid ? t('message.api.connection.successful') : t('message.api.connection.failed') + ' ' + errorMessage
content: valid
? i18n.t('message.api.connection.success')
: i18n.t('message.api.connection.failed') + ' ' + errorMessage
})
return valid

View File

@@ -1,12 +1,12 @@
import { Assistant, Model, Provider, Topic } from '@renderer/types'
import store from '@renderer/store'
import { uuid } from '@renderer/utils'
import i18next from 'i18next'
import i18n from '@renderer/i18n'
export function getDefaultAssistant(): Assistant {
return {
id: 'default',
name: i18next.t('assistant.default.name'),
name: i18n.t('assistant.default.name'),
prompt: '',
topics: [getDefaultTopic()]
}
@@ -15,7 +15,7 @@ export function getDefaultAssistant(): Assistant {
export function getDefaultTopic(): Topic {
return {
id: uuid(),
name: i18next.t('assistant.default.topic.name'),
name: i18n.t('assistant.default.topic.name'),
messages: []
}
}

View File

@@ -9,5 +9,6 @@ export const EVENT_NAMES = {
CLEAR_MESSAGES: 'CLEAR_MESSAGES',
ADD_ASSISTANT: 'ADD_ASSISTANT',
EDIT_MESSAGE: 'EDIT_MESSAGE',
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE'
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED'
}

View File

@@ -19,7 +19,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
version: 7,
version: 9,
blacklist: ['runtime'],
migrate
},

View File

@@ -54,7 +54,7 @@ const initialState: LlmState = {
name: 'ZhiPu',
apiKey: '',
apiHost: 'https://open.bigmodel.cn/api/paas/v4/',
models: SYSTEM_MODELS.groq.filter((m) => m.defaultEnabled),
models: SYSTEM_MODELS.zhipu.filter((m) => m.defaultEnabled),
isSystem: true,
enabled: false
},

View File

@@ -1,6 +1,9 @@
import { createMigrate } from 'redux-persist'
import { RootState } from '.'
import { SYSTEM_MODELS } from '@renderer/config/models'
import { isEmpty } from 'lodash'
import i18n from '@renderer/i18n'
import { Assistant } from '@renderer/types'
const migrate = createMigrate({
// @ts-ignore store type is unknown
@@ -112,6 +115,47 @@ const migrate = createMigrate({
language: navigator.language
}
}
},
// @ts-ignore store type is unknown
'8': (state: RootState) => {
const fixAssistantName = (assistant: Assistant) => {
if (isEmpty(assistant.name)) {
assistant.name = i18n.t(`assistant.${assistant.id}.name`)
}
assistant.topics = assistant.topics.map((topic) => {
if (isEmpty(topic.name)) {
topic.name = i18n.t(`assistant.${assistant.id}.topic.name`)
}
return topic
})
return assistant
}
return {
...state,
assistants: {
...state.assistants,
defaultAssistant: fixAssistantName(state.assistants.defaultAssistant),
assistants: state.assistants.assistants.map((assistant) => fixAssistantName(assistant))
}
}
},
// @ts-ignore store type is unknown
'9': (state: RootState) => {
return {
...state,
llm: {
...state.llm,
providers: state.llm.providers.map((provider) => {
if (provider.id === 'zhipu' && provider.models[0] && provider.models[0].id === 'llama3-70b-8192') {
provider.models = SYSTEM_MODELS.zhipu.filter((m) => m.defaultEnabled)
}
return provider
})
}
}
}
})

View File

@@ -16,7 +16,7 @@ export type Message = {
topicId: string
modelId?: string
createdAt: string
status: 'sending' | 'pending' | 'success' | 'error'
status: 'sending' | 'pending' | 'success' | 'paused' | 'error'
usage?: OpenAI.Completions.CompletionUsage
}

View File

@@ -1051,6 +1051,13 @@ __metadata:
languageName: node
linkType: hard
"@kangfenmao/keyv-storage@npm:^0.1.0":
version: 0.1.0
resolution: "@kangfenmao/keyv-storage@npm:0.1.0"
checksum: 10c0/647cf2d2f2e403ec91d1835546aa08bc6af1468a2823c3aa2cef883bacf67eb1a88bb97be1b4c0a09bc3ed69dba2ccbb8ecc3fd13242e84d4e234d5b77707156
languageName: node
linkType: hard
"@malept/cross-spawn-promise@npm:^1.1.0":
version: 1.1.1
resolution: "@malept/cross-spawn-promise@npm:1.1.1"
@@ -2628,6 +2635,7 @@ __metadata:
"@electron-toolkit/utils": "npm:^3.0.0"
"@fontsource/inter": "npm:^5.0.18"
"@hello-pangea/dnd": "npm:^16.6.0"
"@kangfenmao/keyv-storage": "npm:^0.1.0"
"@reduxjs/toolkit": "npm:^2.2.5"
"@types/lodash": "npm:^4.17.5"
"@types/node": "npm:^18.19.9"