Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b872f58ed | ||
|
|
d5da7e4413 | ||
|
|
3e88aa3c36 | ||
|
|
d6fc1cb364 | ||
|
|
224f23aea0 | ||
|
|
8c186b757e | ||
|
|
3f32775b98 | ||
|
|
067819652b | ||
|
|
b3a023e4ac | ||
|
|
1da1b6622d | ||
|
|
78e1626e52 | ||
|
|
ec49cf61d6 | ||
|
|
b487c68822 | ||
|
|
74aa95339c | ||
|
|
e90ef9d05f | ||
|
|
e8bdf9d5fd | ||
|
|
a97d6f024b | ||
|
|
b4eb35d86a | ||
|
|
3fc45187eb | ||
|
|
f1c5a41fff | ||
|
|
5a7f943448 | ||
|
|
b02e83f68e |
@@ -2,3 +2,4 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
out
|
out
|
||||||
.gitignore
|
.gitignore
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -70,7 +70,6 @@ jobs:
|
|||||||
dist/*.deb
|
dist/*.deb
|
||||||
dist/*.rpm
|
dist/*.rpm
|
||||||
dist/*.tar.gz
|
dist/*.tar.gz
|
||||||
dist/*.yml
|
dist/latest*.yml
|
||||||
dist/*.blockmap
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ pnpm-lock.yaml
|
|||||||
LICENSE.md
|
LICENSE.md
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
tsconfig.*.json
|
tsconfig.*.json
|
||||||
|
CHANGELOG*.md
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -2,6 +2,22 @@
|
|||||||
|
|
||||||
Cherry Studio is a desktop client for multiple cutting-edge LLM models, available on Windows, Mac and Linux.
|
Cherry Studio is a desktop client for multiple cutting-edge LLM models, available on Windows, Mac and Linux.
|
||||||
|
|
||||||
|
# Screenshot
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# 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
|
## 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)
|
- [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)
|
||||||
|
|||||||
@@ -26,8 +26,17 @@ mac:
|
|||||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
notarize: false
|
notarize: false
|
||||||
|
target:
|
||||||
|
- target: dmg
|
||||||
|
arch:
|
||||||
|
- arm64
|
||||||
|
- x64
|
||||||
|
- target: zip
|
||||||
|
arch:
|
||||||
|
- arm64
|
||||||
|
- x64
|
||||||
dmg:
|
dmg:
|
||||||
artifactName: ${productName}-${version}.${ext}
|
artifactName: ${productName}-${version}-${arch}.${ext}
|
||||||
linux:
|
linux:
|
||||||
target:
|
target:
|
||||||
- AppImage
|
- AppImage
|
||||||
@@ -45,3 +54,9 @@ publish:
|
|||||||
electronDownload:
|
electronDownload:
|
||||||
mirror: https://npmmirror.com/mirrors/electron/
|
mirror: https://npmmirror.com/mirrors/electron/
|
||||||
afterSign: scripts/notarize.js
|
afterSign: scripts/notarize.js
|
||||||
|
releaseInfo:
|
||||||
|
releaseNotes: |
|
||||||
|
- 修复多语言提示错误
|
||||||
|
- 修复智谱AI默认模型错误问题
|
||||||
|
- 修复 OpenRouter API 检测出错问题
|
||||||
|
- 修复模型提供商多语言翻译错误问题
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
assetsInclude: ['**/*.md'],
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0'
|
host: '0.0.0.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cherry-studio",
|
"name": "cherry-studio",
|
||||||
"version": "0.2.0",
|
"version": "0.2.3",
|
||||||
"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",
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
"@fontsource/inter": "^5.0.18",
|
"@fontsource/inter": "^5.0.18",
|
||||||
"@hello-pangea/dnd": "^16.6.0",
|
"@hello-pangea/dnd": "^16.6.0",
|
||||||
|
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||||
"@reduxjs/toolkit": "^2.2.5",
|
"@reduxjs/toolkit": "^2.2.5",
|
||||||
"@types/lodash": "^4.17.5",
|
"@types/lodash": "^4.17.5",
|
||||||
"@types/node": "^18.19.9",
|
"@types/node": "^18.19.9",
|
||||||
|
|||||||
@@ -94,7 +94,9 @@ app.whenReady().then(() => {
|
|||||||
.then((name) => console.log(`Added Extension: ${name}`))
|
.then((name) => console.log(`Added Extension: ${name}`))
|
||||||
.catch((err) => console.log('An error occurred: ', err))
|
.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
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ export default class AppUpdater {
|
|||||||
autoUpdater.logger = logger
|
autoUpdater.logger = logger
|
||||||
autoUpdater.forceDevUpdateConfig = true
|
autoUpdater.forceDevUpdateConfig = true
|
||||||
autoUpdater.autoDownload = false
|
autoUpdater.autoDownload = false
|
||||||
autoUpdater.checkForUpdatesAndNotify()
|
autoUpdater.checkForUpdates()
|
||||||
|
|
||||||
// 触发检查更新(此方法用于被渲染线程调用,例如页面点击检查更新按钮来调用此方法)
|
// 触发检查更新(此方法用于被渲染线程调用,例如页面点击检查更新按钮来调用此方法)
|
||||||
ipcMain.on('check-for-update', () => {
|
ipcMain.on('check-for-update', () => {
|
||||||
logger.info('触发检查更新')
|
logger.info('触发检查更新')
|
||||||
autoUpdater.checkForUpdates()
|
return autoUpdater.checkForUpdates()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 检测下载错误
|
// 检测下载错误
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { electronAPI } from '@electron-toolkit/preload'
|
|||||||
|
|
||||||
// Custom APIs for renderer
|
// Custom APIs for renderer
|
||||||
const api = {
|
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
|
// Use `contextBridge` APIs to expose Electron APIs to
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { PersistGate } from 'redux-persist/integration/react'
|
|||||||
import Sidebar from './components/app/Sidebar'
|
import Sidebar from './components/app/Sidebar'
|
||||||
import TopViewContainer from './components/TopView'
|
import TopViewContainer from './components/TopView'
|
||||||
import { AntdThemeConfig, getAntdLocale } from './config/antd'
|
import { AntdThemeConfig, getAntdLocale } from './config/antd'
|
||||||
import './i18n'
|
|
||||||
import AppsPage from './pages/apps/AppsPage'
|
import AppsPage from './pages/apps/AppsPage'
|
||||||
import HomePage from './pages/home/HomePage'
|
import HomePage from './pages/home/HomePage'
|
||||||
import SettingsPage from './pages/settings/SettingsPage'
|
import SettingsPage from './pages/settings/SettingsPage'
|
||||||
|
|||||||
20
src/renderer/src/CHANGELOG.en.md
Normal file
20
src/renderer/src/CHANGELOG.en.md
Normal 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
|
||||||
21
src/renderer/src/CHANGELOG.zh.md
Normal file
21
src/renderer/src/CHANGELOG.zh.md
Normal 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 架构
|
||||||
|
|
||||||
78
src/renderer/src/assets/styles/changelog.module.scss
Normal file
78
src/renderer/src/assets/styles/changelog.module.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/renderer/src/env.d.ts
vendored
2
src/renderer/src/env.d.ts
vendored
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
import { MessageInstance } from 'antd/es/message/interface'
|
import { MessageInstance } from 'antd/es/message/interface'
|
||||||
import { HookAPI } from 'antd/es/modal/useModal'
|
import { HookAPI } from 'antd/es/modal/useModal'
|
||||||
|
import type KeyvStorage from '@kangfenmao/keyv-storage'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
message: MessageInstance
|
message: MessageInstance
|
||||||
modal: HookAPI
|
modal: HookAPI
|
||||||
|
keyv: KeyvStorage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { i18nInit } from '@renderer/i18n'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
import LocalStorage from '@renderer/services/storage'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setAvatar } from '@renderer/store/runtime'
|
import { setAvatar } from '@renderer/store/runtime'
|
||||||
@@ -12,5 +13,6 @@ export function useAppInitEffect() {
|
|||||||
const storedImage = await LocalStorage.getImage('avatar')
|
const storedImage = await LocalStorage.getImage('avatar')
|
||||||
storedImage && dispatch(setAvatar(storedImage))
|
storedImage && dispatch(setAvatar(storedImage))
|
||||||
})
|
})
|
||||||
|
i18nInit()
|
||||||
}, [dispatch])
|
}, [dispatch])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ const resources = {
|
|||||||
'error.enter.api.host': 'Please enter your API host first',
|
'error.enter.api.host': 'Please enter your API host first',
|
||||||
'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'
|
||||||
},
|
},
|
||||||
assistant: {
|
assistant: {
|
||||||
'default.name': 'Default Assistant',
|
'default.name': 'Default Assistant',
|
||||||
@@ -61,7 +62,8 @@ const resources = {
|
|||||||
'input.clear.title': 'Clear all messages?',
|
'input.clear.title': 'Clear all messages?',
|
||||||
'input.clear.content': 'Are you sure to clear all messages?',
|
'input.clear.content': 'Are you sure to clear all messages?',
|
||||||
'input.placeholder': 'Type your message here...',
|
'input.placeholder': 'Type your message here...',
|
||||||
'input.send': 'Send'
|
'input.send': 'Send',
|
||||||
|
'input.pause': 'Pause'
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: 'Agents'
|
title: 'Agents'
|
||||||
@@ -72,8 +74,8 @@ const resources = {
|
|||||||
moonshot: 'Moonshot',
|
moonshot: 'Moonshot',
|
||||||
silicon: 'SiliconFlow',
|
silicon: 'SiliconFlow',
|
||||||
openrouter: 'OpenRouter',
|
openrouter: 'OpenRouter',
|
||||||
yi: 'Lingyiwanwu',
|
yi: 'Yi',
|
||||||
zhipu: 'BigModel',
|
zhipu: 'ZHIPU AI',
|
||||||
groq: 'Groq',
|
groq: 'Groq',
|
||||||
ollama: 'Ollama'
|
ollama: 'Ollama'
|
||||||
},
|
},
|
||||||
@@ -146,7 +148,8 @@ const resources = {
|
|||||||
'error.enter.api.host': '请输入您的 API 地址',
|
'error.enter.api.host': '请输入您的 API 地址',
|
||||||
'error.enter.model': '请选择一个模型',
|
'error.enter.model': '请选择一个模型',
|
||||||
'api.connection.failed': '连接失败',
|
'api.connection.failed': '连接失败',
|
||||||
'api.connection.successful': '连接成功'
|
'api.connection.successful': '连接成功',
|
||||||
|
'chat.completion.paused': '会话已停止'
|
||||||
},
|
},
|
||||||
assistant: {
|
assistant: {
|
||||||
'default.name': '默认助手',
|
'default.name': '默认助手',
|
||||||
@@ -168,7 +171,8 @@ const resources = {
|
|||||||
'input.clear.title': '清除所有消息?',
|
'input.clear.title': '清除所有消息?',
|
||||||
'input.clear.content': '确定要清除所有消息吗?',
|
'input.clear.content': '确定要清除所有消息吗?',
|
||||||
'input.placeholder': '在这里输入消息...',
|
'input.placeholder': '在这里输入消息...',
|
||||||
'input.send': '发送'
|
'input.send': '发送',
|
||||||
|
'input.pause': '暂停'
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: '智能体'
|
title: '智能体'
|
||||||
@@ -221,11 +225,15 @@ const resources = {
|
|||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
resources,
|
resources,
|
||||||
lng: store.getState().settings.language || 'en-US',
|
lng: localStorage.getItem('language') || navigator.language || 'en-US',
|
||||||
fallbackLng: 'en-US',
|
fallbackLng: 'en-US',
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false
|
escapeValue: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export function i18nInit() {
|
||||||
|
i18n.changeLanguage(store.getState().settings.language || 'en-US')
|
||||||
|
}
|
||||||
|
|
||||||
export default i18n
|
export default i18n
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
|
import KeyvStorage from '@kangfenmao/keyv-storage'
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
localforage.config({
|
localforage.config({
|
||||||
@@ -8,6 +9,8 @@ function init() {
|
|||||||
storeName: 'cherryai',
|
storeName: 'cherryai',
|
||||||
description: 'Cherry Studio Storage'
|
description: 'Cherry Studio Storage'
|
||||||
})
|
})
|
||||||
|
window.keyv = new KeyvStorage()
|
||||||
|
window.keyv.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'
|
|||||||
import App from './App'
|
import App from './App'
|
||||||
import './assets/styles/index.scss'
|
import './assets/styles/index.scss'
|
||||||
import './init'
|
import './init'
|
||||||
|
import './i18n'
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
FullscreenExitOutlined,
|
FullscreenExitOutlined,
|
||||||
FullscreenOutlined,
|
FullscreenOutlined,
|
||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
|
PauseCircleOutlined,
|
||||||
PlusCircleOutlined
|
PlusCircleOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||||
@@ -19,9 +20,10 @@ import { 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'
|
||||||
import { useAppSelector } from '@renderer/store'
|
import store, { useAppSelector } from '@renderer/store'
|
||||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@@ -86,6 +88,11 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
|
|
||||||
const clearTopic = () => EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES)
|
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
|
// Command or Ctrl + N create new topic
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeydown = (e) => {
|
const onKeydown = (e) => {
|
||||||
@@ -148,6 +155,13 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolbarMenu>
|
</ToolbarMenu>
|
||||||
<ToolbarMenu>
|
<ToolbarMenu>
|
||||||
|
{generating && (
|
||||||
|
<Tooltip placement="top" title={t('assistant.input.pause')} arrow>
|
||||||
|
<ToolbarButton type="text" onClick={onPause}>
|
||||||
|
<PauseCircleOutlined />
|
||||||
|
</ToolbarButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<SendMessageSetting>
|
<SendMessageSetting>
|
||||||
<ToolbarButton type="text" style={{ marginRight: 0 }}>
|
<ToolbarButton type="text" style={{ marginRight: 0 }}>
|
||||||
<MoreOutlined />
|
<MoreOutlined />
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Logo from '@renderer/assets/images/logo.png'
|
|||||||
import { SyncOutlined } from '@ant-design/icons'
|
import { SyncOutlined } from '@ant-design/icons'
|
||||||
import { firstLetter } from '@renderer/utils'
|
import { firstLetter } from '@renderer/utils'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: Message
|
message: Message
|
||||||
@@ -53,6 +54,13 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
|||||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.REGENERATE_MESSAGE), 100)
|
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 (
|
return (
|
||||||
<MessageContainer key={message.id}>
|
<MessageContainer key={message.id}>
|
||||||
<AvatarWrapper>
|
<AvatarWrapper>
|
||||||
@@ -72,7 +80,7 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
|||||||
)}
|
)}
|
||||||
{message.status !== 'sending' && (
|
{message.status !== 'sending' && (
|
||||||
<Markdown className="markdown" components={{ code: CodeBlock as any }}>
|
<Markdown className="markdown" components={{ code: CodeBlock as any }}>
|
||||||
{message.content}
|
{getMessageContent(message)}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
)}
|
)}
|
||||||
{showMenu && (
|
{showMenu && (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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'
|
||||||
|
|
||||||
const AboutSettings: FC = () => {
|
const AboutSettings: FC = () => {
|
||||||
const [version, setVersion] = useState('')
|
const [version, setVersion] = useState('')
|
||||||
@@ -23,6 +24,7 @@ const AboutSettings: FC = () => {
|
|||||||
Cherry Studio <Version>(v{version})</Version>
|
Cherry Studio <Version>(v{version})</Version>
|
||||||
</Title>
|
</Title>
|
||||||
<Description>{t('settings.about.description')}</Description>
|
<Description>{t('settings.about.description')}</Description>
|
||||||
|
<Changelog />
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from './components'
|
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 styled from 'styled-components'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
import LocalStorage from '@renderer/services/storage'
|
||||||
import { compressImage } from '@renderer/utils'
|
import { compressImage } from '@renderer/utils'
|
||||||
@@ -14,7 +14,6 @@ import i18next from 'i18next'
|
|||||||
|
|
||||||
const GeneralSettings: FC = () => {
|
const GeneralSettings: FC = () => {
|
||||||
const avatar = useAvatar()
|
const avatar = useAvatar()
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
|
||||||
const { language } = useSettings()
|
const { language } = useSettings()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -22,14 +21,26 @@ const GeneralSettings: FC = () => {
|
|||||||
const onSelectLanguage = (value: string) => {
|
const onSelectLanguage = (value: string) => {
|
||||||
dispatch(setLanguage(value))
|
dispatch(setLanguage(value))
|
||||||
i18next.changeLanguage(value)
|
i18next.changeLanguage(value)
|
||||||
// window.location.reload()
|
localStorage.setItem('language', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
{contextHolder}
|
|
||||||
<SettingTitle>{t('settings.general.title')}</SettingTitle>
|
<SettingTitle>{t('settings.general.title')}</SettingTitle>
|
||||||
<SettingDivider />
|
<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>
|
<SettingRow>
|
||||||
<SettingRowTitle>{t('common.avatar')}</SettingRowTitle>
|
<SettingRowTitle>{t('common.avatar')}</SettingRowTitle>
|
||||||
<Upload
|
<Upload
|
||||||
@@ -44,29 +55,13 @@ const GeneralSettings: FC = () => {
|
|||||||
await LocalStorage.storeImage('avatar', compressedFile)
|
await LocalStorage.storeImage('avatar', compressedFile)
|
||||||
dispatch(setAvatar(await LocalStorage.getImage('avatar')))
|
dispatch(setAvatar(await LocalStorage.getImage('avatar')))
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
messageApi.open({
|
window.message.error(error.message)
|
||||||
type: 'error',
|
|
||||||
content: error.message
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<UserAvatar src={avatar} size="large" />
|
<UserAvatar src={avatar} size="large" />
|
||||||
</Upload>
|
</Upload>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<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>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/renderer/src/pages/settings/components/Changelog.tsx
Normal file
29
src/renderer/src/pages/settings/components/Changelog.tsx
Normal 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
|
||||||
@@ -86,7 +86,7 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
|||||||
return (
|
return (
|
||||||
<Flex>
|
<Flex>
|
||||||
<ModelHeaderTitle>
|
<ModelHeaderTitle>
|
||||||
{provider.name} {t('common.models')}
|
{t(`provider.${provider.id}`)} {t('common.models')}
|
||||||
</ModelHeaderTitle>
|
</ModelHeaderTitle>
|
||||||
{loading && <LoadingOutlined size={20} />}
|
{loading && <LoadingOutlined size={20} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ interface Props {
|
|||||||
provider: Provider
|
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 [apiKey, setApiKey] = useState(provider.apiKey)
|
||||||
const [apiHost, setApiHost] = useState(provider.apiHost)
|
const [apiHost, setApiHost] = useState(provider.apiHost)
|
||||||
const [apiValid, setApiValid] = useState(false)
|
const [apiValid, setApiValid] = useState(false)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { takeRight } from 'lodash'
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { setGenerating } from '@renderer/store/runtime'
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
import { t } from 'i18next'
|
import i18n from '@renderer/i18n'
|
||||||
|
|
||||||
interface FetchChatCompletionParams {
|
interface FetchChatCompletionParams {
|
||||||
messages: Message[]
|
messages: Message[]
|
||||||
@@ -27,6 +27,8 @@ const getOpenAiProvider = (provider: Provider) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchChatCompletion({ messages, topic, assistant, onResponse }: FetchChatCompletionParams) {
|
export async function fetchChatCompletion({ messages, topic, assistant, onResponse }: FetchChatCompletionParams) {
|
||||||
|
window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, false)
|
||||||
|
|
||||||
const provider = getAssistantProvider(assistant)
|
const provider = getAssistantProvider(assistant)
|
||||||
const openaiProvider = getOpenAiProvider(provider)
|
const openaiProvider = getOpenAiProvider(provider)
|
||||||
const defaultModel = getDefaultModel()
|
const defaultModel = getDefaultModel()
|
||||||
@@ -34,7 +36,7 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
|
|||||||
|
|
||||||
store.dispatch(setGenerating(true))
|
store.dispatch(setGenerating(true))
|
||||||
|
|
||||||
const _message: Message = {
|
const message: Message = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: '',
|
content: '',
|
||||||
@@ -45,7 +47,7 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
|
|||||||
status: 'sending'
|
status: 'sending'
|
||||||
}
|
}
|
||||||
|
|
||||||
onResponse({ ..._message })
|
onResponse({ ...message })
|
||||||
|
|
||||||
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
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
|
content: message.content
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const _messages = [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[]
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stream = await openaiProvider.chat.completions.create({
|
const stream = await openaiProvider.chat.completions.create({
|
||||||
model: model.id,
|
model: model.id,
|
||||||
messages: _messages,
|
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
|
||||||
stream: true
|
stream: true
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,22 +67,27 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
|
|||||||
let usage: OpenAI.Completions.CompletionUsage | undefined = undefined
|
let usage: OpenAI.Completions.CompletionUsage | undefined = undefined
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
|
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
content = content + (chunk.choices[0]?.delta?.content || '')
|
content = content + (chunk.choices[0]?.delta?.content || '')
|
||||||
chunk.usage && (usage = chunk.usage)
|
chunk.usage && (usage = chunk.usage)
|
||||||
onResponse({ ..._message, content, status: 'pending' })
|
onResponse({ ...message, content, status: 'pending' })
|
||||||
}
|
}
|
||||||
|
|
||||||
_message.content = content
|
message.content = content
|
||||||
_message.usage = usage
|
message.usage = usage
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
_message.content = `Error: ${error.message}`
|
message.content = `Error: ${error.message}`
|
||||||
}
|
}
|
||||||
|
|
||||||
_message.status = 'success'
|
const paused = window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)
|
||||||
EventEmitter.emit(EVENT_NAMES.AI_CHAT_COMPLETION, _message)
|
message.status = paused ? 'paused' : 'success'
|
||||||
|
EventEmitter.emit(EVENT_NAMES.AI_CHAT_COMPLETION, message)
|
||||||
store.dispatch(setGenerating(false))
|
store.dispatch(setGenerating(false))
|
||||||
|
|
||||||
return _message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FetchMessagesSummaryParams {
|
interface FetchMessagesSummaryParams {
|
||||||
@@ -122,17 +127,17 @@ export async function checkApi(provider: Provider) {
|
|||||||
const style = { marginTop: '3vh' }
|
const style = { marginTop: '3vh' }
|
||||||
|
|
||||||
if (!provider.apiKey) {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!provider.apiHost) {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!model) {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +147,8 @@ export async function checkApi(provider: Provider) {
|
|||||||
try {
|
try {
|
||||||
const response = await openaiProvider.chat.completions.create({
|
const response = await openaiProvider.chat.completions.create({
|
||||||
model: model.id,
|
model: model.id,
|
||||||
messages: [{ role: 'user', content: 'hello' }],
|
messages: [{ role: 'user', content: 'hi' }],
|
||||||
|
max_tokens: 100,
|
||||||
stream: false
|
stream: false
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -156,7 +162,9 @@ export async function checkApi(provider: Provider) {
|
|||||||
key: 'api-check',
|
key: 'api-check',
|
||||||
style: { marginTop: '3vh' },
|
style: { marginTop: '3vh' },
|
||||||
duration: valid ? 2 : 8,
|
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
|
return valid
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Assistant, Model, Provider, Topic } from '@renderer/types'
|
import { Assistant, Model, Provider, Topic } from '@renderer/types'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { uuid } from '@renderer/utils'
|
import { uuid } from '@renderer/utils'
|
||||||
import i18next from 'i18next'
|
import i18n from '@renderer/i18n'
|
||||||
|
|
||||||
export function getDefaultAssistant(): Assistant {
|
export function getDefaultAssistant(): Assistant {
|
||||||
return {
|
return {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
name: i18next.t('assistant.default.name'),
|
name: i18n.t('assistant.default.name'),
|
||||||
prompt: '',
|
prompt: '',
|
||||||
topics: [getDefaultTopic()]
|
topics: [getDefaultTopic()]
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ export function getDefaultAssistant(): Assistant {
|
|||||||
export function getDefaultTopic(): Topic {
|
export function getDefaultTopic(): Topic {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name: i18next.t('assistant.default.topic.name'),
|
name: i18n.t('assistant.default.topic.name'),
|
||||||
messages: []
|
messages: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ export const EVENT_NAMES = {
|
|||||||
CLEAR_MESSAGES: 'CLEAR_MESSAGES',
|
CLEAR_MESSAGES: 'CLEAR_MESSAGES',
|
||||||
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'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 7,
|
version: 9,
|
||||||
blacklist: ['runtime'],
|
blacklist: ['runtime'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const initialState: LlmState = {
|
|||||||
name: 'ZhiPu',
|
name: 'ZhiPu',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: 'https://open.bigmodel.cn/api/paas/v4/',
|
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,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { createMigrate } from 'redux-persist'
|
import { createMigrate } from 'redux-persist'
|
||||||
import { RootState } from '.'
|
import { RootState } from '.'
|
||||||
import { SYSTEM_MODELS } from '@renderer/config/models'
|
import { SYSTEM_MODELS } from '@renderer/config/models'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import i18n from '@renderer/i18n'
|
||||||
|
import { Assistant } from '@renderer/types'
|
||||||
|
|
||||||
const migrate = createMigrate({
|
const migrate = createMigrate({
|
||||||
// @ts-ignore store type is unknown
|
// @ts-ignore store type is unknown
|
||||||
@@ -112,6 +115,47 @@ const migrate = createMigrate({
|
|||||||
language: navigator.language
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export type Message = {
|
|||||||
topicId: string
|
topicId: string
|
||||||
modelId?: string
|
modelId?: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
status: 'sending' | 'pending' | 'success' | 'error'
|
status: 'sending' | 'pending' | 'success' | 'paused' | 'error'
|
||||||
usage?: OpenAI.Completions.CompletionUsage
|
usage?: OpenAI.Completions.CompletionUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1051,6 +1051,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@malept/cross-spawn-promise@npm:^1.1.0":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@malept/cross-spawn-promise@npm:1.1.1"
|
resolution: "@malept/cross-spawn-promise@npm:1.1.1"
|
||||||
@@ -2628,6 +2635,7 @@ __metadata:
|
|||||||
"@electron-toolkit/utils": "npm:^3.0.0"
|
"@electron-toolkit/utils": "npm:^3.0.0"
|
||||||
"@fontsource/inter": "npm:^5.0.18"
|
"@fontsource/inter": "npm:^5.0.18"
|
||||||
"@hello-pangea/dnd": "npm:^16.6.0"
|
"@hello-pangea/dnd": "npm:^16.6.0"
|
||||||
|
"@kangfenmao/keyv-storage": "npm:^0.1.0"
|
||||||
"@reduxjs/toolkit": "npm:^2.2.5"
|
"@reduxjs/toolkit": "npm:^2.2.5"
|
||||||
"@types/lodash": "npm:^4.17.5"
|
"@types/lodash": "npm:^4.17.5"
|
||||||
"@types/node": "npm:^18.19.9"
|
"@types/node": "npm:^18.19.9"
|
||||||
|
|||||||
Reference in New Issue
Block a user