feat: add i18n
This commit is contained in:
parent
6c8d2b0f68
commit
4a0b394bf5
@ -37,11 +37,11 @@ function createWindow(): void {
|
|||||||
|
|
||||||
mainWindow.webContents.on('context-menu', () => {
|
mainWindow.webContents.on('context-menu', () => {
|
||||||
const menu = new Menu()
|
const menu = new Menu()
|
||||||
menu.append(new MenuItem({ label: 'Copy', role: 'copy' }))
|
menu.append(new MenuItem({ label: '复制', role: 'copy', sublabel: '⌘ + C' }))
|
||||||
menu.append(new MenuItem({ label: 'Paste', role: 'paste' }))
|
menu.append(new MenuItem({ label: '粘贴', role: 'paste', sublabel: '⌘ + V' }))
|
||||||
menu.append(new MenuItem({ label: 'Cut', role: 'cut' }))
|
menu.append(new MenuItem({ label: '剪切', role: 'cut', sublabel: '⌘ + X' }))
|
||||||
menu.append(new MenuItem({ type: 'separator' }))
|
menu.append(new MenuItem({ type: 'separator' }))
|
||||||
menu.append(new MenuItem({ label: 'Select All', role: 'selectAll' }))
|
menu.append(new MenuItem({ label: '全选', role: 'selectAll', sublabel: '⌘ + A' }))
|
||||||
menu.popup()
|
menu.popup()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ function createWindow(): void {
|
|||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
// Set app user model id for windows
|
// Set app user model id for windows
|
||||||
electronApp.setAppUserModelId('com.electron')
|
electronApp.setAppUserModelId('com.kangfenmao.CherryStudio')
|
||||||
|
|
||||||
// Default open or close DevTools by F12 in development
|
// Default open or close DevTools by F12 in development
|
||||||
// and ignore CommandOrControl + R in production.
|
// and ignore CommandOrControl + R in production.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Cherry Studio</title>
|
<title>Cherry Studio</title>
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
import '@fontsource/inter'
|
import '@fontsource/inter'
|
||||||
import store, { persistor } from '@renderer/store'
|
import store, { persistor } from '@renderer/store'
|
||||||
|
import { ConfigProvider } from 'antd'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { HashRouter, Route, Routes } from 'react-router-dom'
|
import { HashRouter, Route, Routes } from 'react-router-dom'
|
||||||
import { PersistGate } from 'redux-persist/integration/react'
|
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 { 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'
|
||||||
import { ConfigProvider } from 'antd'
|
|
||||||
import TopViewContainer from './components/TopView'
|
|
||||||
import { AntdThemeConfig } from './config/antd'
|
|
||||||
import './i18n'
|
|
||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<ConfigProvider theme={AntdThemeConfig}>
|
<ConfigProvider theme={AntdThemeConfig} locale={getAntdLocale()}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
<TopViewContainer>
|
<TopViewContainer>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { TopView } from '../TopView'
|
|||||||
import { Box } from '../Layout'
|
import { Box } from '../Layout'
|
||||||
import { Assistant } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface AssistantSettingPopupShowParams {
|
interface AssistantSettingPopupShowParams {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@ -18,6 +19,7 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve })
|
|||||||
const [description, setDescription] = useState(assistant.description)
|
const [description, setDescription] = useState(assistant.description)
|
||||||
const [prompt, setPrompt] = useState(assistant.prompt)
|
const [prompt, setPrompt] = useState(assistant.prompt)
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onOk = () => {
|
const onOk = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@ -33,21 +35,30 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title={assistant.name} open={open} onOk={onOk} onCancel={handleCancel} afterClose={onClose}>
|
<Modal title={assistant.name} open={open} onOk={onOk} onCancel={handleCancel} afterClose={onClose}>
|
||||||
<Box mb={8}>Name</Box>
|
<Box mb={8}>{t('common.name')}</Box>
|
||||||
<Input placeholder="Assistant Name" value={name} onChange={(e) => setName(e.target.value)} />
|
<Input
|
||||||
|
placeholder={t('common.assistant') + t('common.name')}
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
<Box mt={8} mb={8}>
|
<Box mt={8} mb={8}>
|
||||||
Description
|
{t('common.description')}
|
||||||
</Box>
|
</Box>
|
||||||
<TextArea
|
<TextArea
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder="Assistant Description"
|
placeholder={t('common.assistant') + t('common.description')}
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Box mt={8} mb={8}>
|
<Box mt={8} mb={8}>
|
||||||
Prompt
|
{t('common.prompt')}
|
||||||
</Box>
|
</Box>
|
||||||
<TextArea rows={4} placeholder="Assistant Prompt" value={prompt} onChange={(e) => setPrompt(e.target.value)} />
|
<TextArea
|
||||||
|
rows={4}
|
||||||
|
placeholder={t('common.assistant') + t('common.prompt')}
|
||||||
|
value={prompt}
|
||||||
|
onChange={(e) => setPrompt(e.target.value)}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
import store from '@renderer/store'
|
||||||
import { theme, ThemeConfig } from 'antd'
|
import { theme, ThemeConfig } from 'antd'
|
||||||
|
import zhCN from 'antd/locale/zh_CN'
|
||||||
|
|
||||||
export const colorPrimary = '#00b96b'
|
export const colorPrimary = '#00b96b'
|
||||||
|
|
||||||
@ -9,3 +11,16 @@ export const AntdThemeConfig: ThemeConfig = {
|
|||||||
},
|
},
|
||||||
algorithm: [theme.darkAlgorithm]
|
algorithm: [theme.darkAlgorithm]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAntdLocale() {
|
||||||
|
const language = store.getState().settings.language
|
||||||
|
|
||||||
|
switch (language) {
|
||||||
|
case 'zh-CN':
|
||||||
|
return zhCN
|
||||||
|
case 'en-US':
|
||||||
|
return undefined
|
||||||
|
default:
|
||||||
|
return zhCN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,31 +1,28 @@
|
|||||||
import { SystemAssistant } from '@renderer/types'
|
import { SystemAssistant } from '@renderer/types'
|
||||||
|
|
||||||
export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
|
export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
|
||||||
// Article
|
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D29',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D29',
|
||||||
name: '文章总结',
|
name: '文章总结',
|
||||||
description: '自动总结文章内容,帮助读者从中获取更多的信息',
|
description: '自动总结文章内容,帮助读者从中获取更多的信息',
|
||||||
prompt: '总结下面的文章,给出总结、摘要、观点三个部分内容,其中观点部分要使用列表列出,使用 Markdown 回复',
|
prompt: '总结下面的文章,给出总结、摘要、观点三个部分内容,其中观点部分要使用列表列出,使用 Markdown 回复',
|
||||||
group: 'Article'
|
group: '文章'
|
||||||
},
|
},
|
||||||
// Writing
|
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D30',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D30',
|
||||||
name: '论文',
|
name: '论文',
|
||||||
description: '根据主题撰写内容翔实、有信服力的论文',
|
description: '根据主题撰写内容翔实、有信服力的论文',
|
||||||
prompt:
|
prompt:
|
||||||
'我希望你能作为一名学者行事。你将负责研究一个你选择的主题,并将研究结果以论文或文章的形式呈现出来。你的任务是确定可靠的来源,以结构良好的方式组织材料,并以引用的方式准确记录。',
|
'我希望你能作为一名学者行事。你将负责研究一个你选择的主题,并将研究结果以论文或文章的形式呈现出来。你的任务是确定可靠的来源,以结构良好的方式组织材料,并以引用的方式准确记录。',
|
||||||
group: 'Writing'
|
group: '写作'
|
||||||
},
|
},
|
||||||
// Translation
|
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D40',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D40',
|
||||||
name: '翻译成中文',
|
name: '翻译成中文',
|
||||||
description: '你是一个好用的翻译助手, 可以把任何语言翻译成中文',
|
description: '你是一个好用的翻译助手, 可以把任何语言翻译成中文',
|
||||||
prompt:
|
prompt:
|
||||||
'你是一个好用的翻译助手。请将我的英文翻译成中文,将所有非中文的翻译成中文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合中文的语言习惯。',
|
'你是一个好用的翻译助手。请将我的英文翻译成中文,将所有非中文的翻译成中文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合中文的语言习惯。',
|
||||||
group: 'Translation'
|
group: '翻译'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D41',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D41',
|
||||||
@ -33,16 +30,15 @@ export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
|
|||||||
description: '你是一个好用的翻译助手, 可以把任何语言翻译成英文',
|
description: '你是一个好用的翻译助手, 可以把任何语言翻译成英文',
|
||||||
prompt:
|
prompt:
|
||||||
'你是一个好用的翻译助手。请将我的中文翻译成英文,将所有非中文的翻译成英文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合英文的语言习惯。',
|
'你是一个好用的翻译助手。请将我的中文翻译成英文,将所有非中文的翻译成英文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合英文的语言习惯。',
|
||||||
group: 'Translation'
|
group: '翻译'
|
||||||
},
|
},
|
||||||
// Software Engineer
|
|
||||||
{
|
{
|
||||||
id: '43CEDACF-C9EB-431B-848C-4D08EC26EB90',
|
id: '43CEDACF-C9EB-431B-848C-4D08EC26EB90',
|
||||||
name: '软件工程师',
|
name: '软件工程师',
|
||||||
description: '高级软件工程师,可以解答各种技术问题',
|
description: '高级软件工程师,可以解答各种技术问题',
|
||||||
prompt:
|
prompt:
|
||||||
'你是一个高级软件工程师,你需要帮我解答各种技术难题、设计技术方案以及编写代码。你编写的代码必须可以正常运行,而且没有任何 Bug 和其他问题。',
|
'你是一个高级软件工程师,你需要帮我解答各种技术难题、设计技术方案以及编写代码。你编写的代码必须可以正常运行,而且没有任何 Bug 和其他问题。',
|
||||||
group: 'Software Engineer'
|
group: '软件工程师'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2A',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2A',
|
||||||
@ -50,7 +46,7 @@ export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
|
|||||||
description: '高级前端工程师,可以解答各种技术问题',
|
description: '高级前端工程师,可以解答各种技术问题',
|
||||||
prompt:
|
prompt:
|
||||||
'你擅长使用 TypeScript, JavaScript, HMLT, CSS 等编程语言。同时你还会使用 Node.js 及各种包来解决开发中遇到的问题。你还会使用 React, Vue 等前端框架。对于我的问题希望你能给出具体的代码示例,最好能够封装成一个函数方便我复制运行测试。',
|
'你擅长使用 TypeScript, JavaScript, HMLT, CSS 等编程语言。同时你还会使用 Node.js 及各种包来解决开发中遇到的问题。你还会使用 React, Vue 等前端框架。对于我的问题希望你能给出具体的代码示例,最好能够封装成一个函数方便我复制运行测试。',
|
||||||
group: 'Software Engineer'
|
group: '软件工程师'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2B',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2B',
|
||||||
@ -58,98 +54,97 @@ export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
|
|||||||
description: '高级后端工程师,可以解答各种技术问题',
|
description: '高级后端工程师,可以解答各种技术问题',
|
||||||
prompt:
|
prompt:
|
||||||
'高级后端工程师,技术难题解答,服务器架构,数据库优化,API设计,网络安全,代码审查,性能调优,微服务,分布式系统,容器技术,持续集成/持续部署(CI/CD)。',
|
'高级后端工程师,技术难题解答,服务器架构,数据库优化,API设计,网络安全,代码审查,性能调优,微服务,分布式系统,容器技术,持续集成/持续部署(CI/CD)。',
|
||||||
group: 'Software Engineer'
|
group: '软件工程师'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2D',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2D',
|
||||||
name: '测试工程师',
|
name: '测试工程师',
|
||||||
description: '高级测试工程师,可以解答各种测试相关问题',
|
description: '高级测试工程师,可以解答各种测试相关问题',
|
||||||
prompt: '你是一个高级测试工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级测试工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Software Engineer'
|
group: '软件工程师'
|
||||||
},
|
},
|
||||||
// Programming Languages Assistants
|
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2E',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2E',
|
||||||
name: 'Python 工程师',
|
name: 'Python 工程师',
|
||||||
description: '你是一个高级Python工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级Python工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级Python工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级Python工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2F',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2F',
|
||||||
name: 'Java 工程师',
|
name: 'Java 工程师',
|
||||||
description: '你是一个高级Java工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级Java工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级Java工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级Java工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D30',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D30',
|
||||||
name: 'C# 工程师',
|
name: 'C# 工程师',
|
||||||
description: '你是一个高级C#工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级C#工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级C#工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级C#工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D31',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D31',
|
||||||
name: 'C++ 工程师',
|
name: 'C++ 工程师',
|
||||||
description: '你是一个高级C++工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级C++工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级C++工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级C++工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D32',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D32',
|
||||||
name: 'C 工程师',
|
name: 'C 工程师',
|
||||||
description: '你是一个高级C工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级C工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级C工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级C工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D33',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D33',
|
||||||
name: 'Go 工程师',
|
name: 'Go 工程师',
|
||||||
description: '你是一个高级Go工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级Go工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级Go工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级Go工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D34',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D34',
|
||||||
name: 'Rust 工程师',
|
name: 'Rust 工程师',
|
||||||
description: '你是一个高级Rust工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级Rust工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级Rust工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级Rust工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D35',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D35',
|
||||||
name: 'PHP 工程师',
|
name: 'PHP 工程师',
|
||||||
description: '你是一个高级PHP工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级PHP工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级PHP工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级PHP工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D36',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D36',
|
||||||
name: 'Ruby 工程师',
|
name: 'Ruby 工程师',
|
||||||
description: '你是一个高级Ruby工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级Ruby工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级Ruby工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级Ruby工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D37',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D37',
|
||||||
name: 'Swift 工程师',
|
name: 'Swift 工程师',
|
||||||
description: '你是一个高级Swift工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级Swift工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级Swift工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级Swift工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D38',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D38',
|
||||||
name: 'Kotlin 工程师',
|
name: 'Kotlin 工程师',
|
||||||
description: '你是一个高级Kotlin工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级Kotlin工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级Kotlin工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级Kotlin工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D39',
|
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D39',
|
||||||
name: 'Dart 工程师',
|
name: 'Dart 工程师',
|
||||||
description: '你是一个高级Dart工程师,你需要帮我解答各种技术难题',
|
description: '你是一个高级Dart工程师,你需要帮我解答各种技术难题',
|
||||||
prompt: '你是一个高级Dart工程师,你需要帮我解答各种技术难题',
|
prompt: '你是一个高级Dart工程师,你需要帮我解答各种技术难题',
|
||||||
group: 'Programming Languages'
|
group: '编程语言'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export const DEFAULT_TOPIC_NAME = 'Default Topic'
|
|
||||||
73
src/renderer/src/config/provider.ts
Normal file
73
src/renderer/src/config/provider.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
export const PROVIDER_CONFIG = {
|
||||||
|
openai: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://openai.com/',
|
||||||
|
apiKey: 'https://platform.openai.com/api-keys',
|
||||||
|
docs: 'https://platform.openai.com/docs',
|
||||||
|
models: 'https://platform.openai.com/docs/models'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
silicon: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://www.siliconflow.cn/',
|
||||||
|
apiKey: 'https://cloud.siliconflow.cn/account/ak',
|
||||||
|
docs: 'https://docs.siliconflow.cn/',
|
||||||
|
models: 'https://docs.siliconflow.cn/docs/model-names'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deepseek: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://deepseek.com/',
|
||||||
|
apiKey: 'https://platform.deepseek.com/api_keys',
|
||||||
|
docs: 'https://platform.deepseek.com/api-docs/',
|
||||||
|
models: 'https://platform.deepseek.com/api-docs/'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yi: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://platform.lingyiwanwu.com/',
|
||||||
|
apiKey: 'https://platform.lingyiwanwu.com/apikeys',
|
||||||
|
docs: 'https://platform.lingyiwanwu.com/docs',
|
||||||
|
models: 'https://platform.lingyiwanwu.com/docs#%E6%A8%A1%E5%9E%8B'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zhipu: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://open.bigmodel.cn/',
|
||||||
|
apiKey: 'https://open.bigmodel.cn/usercenter/apikeys',
|
||||||
|
docs: 'https://open.bigmodel.cn/dev/howuse/introduction',
|
||||||
|
models: 'https://open.bigmodel.cn/modelcenter/square'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moonshot: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://moonshot.ai/',
|
||||||
|
apiKey: 'https://platform.moonshot.cn/console/api-keys',
|
||||||
|
docs: 'https://platform.moonshot.cn/docs/',
|
||||||
|
models: 'https://platform.moonshot.cn/docs/intro#%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openrouter: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://openrouter.ai/',
|
||||||
|
apiKey: 'https://openrouter.ai/settings/keys',
|
||||||
|
docs: 'https://openrouter.ai/docs/quick-start',
|
||||||
|
models: 'https://openrouter.ai/docs/models'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
groq: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://groq.com/',
|
||||||
|
apiKey: 'https://console.groq.com/keys',
|
||||||
|
docs: 'https://console.groq.com/docs/quickstart',
|
||||||
|
models: 'https://console.groq.com/docs/models'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ollama: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://ollama.com/',
|
||||||
|
docs: 'https://github.com/ollama/ollama/tree/main/docs',
|
||||||
|
models: 'https://ollama.com/library'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,7 +11,6 @@ export function useAppInitEffect() {
|
|||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
const storedImage = await LocalStorage.getImage('avatar')
|
const storedImage = await LocalStorage.getImage('avatar')
|
||||||
storedImage && dispatch(setAvatar(storedImage))
|
storedImage && dispatch(setAvatar(storedImage))
|
||||||
console.debug('Avatar loaded from storage')
|
|
||||||
})
|
})
|
||||||
}, [dispatch])
|
}, [dispatch])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,217 @@
|
|||||||
|
import store from '@renderer/store'
|
||||||
import i18n from 'i18next'
|
import i18n from 'i18next'
|
||||||
import { initReactI18next } from 'react-i18next'
|
import { initReactI18next } from 'react-i18next'
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
'en-US': {
|
'en-US': {
|
||||||
translation: {
|
translation: {
|
||||||
|
common: {
|
||||||
|
avatar: 'Avatar',
|
||||||
|
language: 'Language',
|
||||||
|
model: 'Model',
|
||||||
|
models: 'Models',
|
||||||
|
topics: 'Topics',
|
||||||
|
docs: 'Docs',
|
||||||
|
and: 'and',
|
||||||
|
assistant: 'Assistant',
|
||||||
|
name: 'Name',
|
||||||
|
description: 'Description',
|
||||||
|
prompt: 'Prompt',
|
||||||
|
rename: 'Rename',
|
||||||
|
delete: 'Delete',
|
||||||
|
edit: 'Edit',
|
||||||
|
duplicate: 'Duplicate',
|
||||||
|
copy: 'Copy',
|
||||||
|
regenerate: 'Regenerate',
|
||||||
|
provider: 'Provider'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
add: 'Add',
|
||||||
|
manage: 'Manage',
|
||||||
|
select_model: 'Select Model'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
copied: 'Copied!',
|
||||||
|
'assistant.added.content': 'Assistant added successfully',
|
||||||
|
'message.delete.title': 'Delete Message',
|
||||||
|
'message.delete.content': 'Are you sure you want to delete this message?',
|
||||||
|
'error.enter.api.key': 'Please enter your API key first',
|
||||||
|
'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'
|
||||||
|
},
|
||||||
|
assistant: {
|
||||||
|
'default.name': 'Default Assistant',
|
||||||
|
'default.description': "Hello, I'm Default Assistant. You can start chatting with me right away",
|
||||||
|
'default.topic.name': 'Default Topic',
|
||||||
|
'topics.title': 'Topics',
|
||||||
|
'topics.hide_topics': 'Hide Topics',
|
||||||
|
'topics.show_topics': 'Show Topics',
|
||||||
|
'topics.auto_rename': 'Auto Rename',
|
||||||
|
'topics.edit.title': 'Rename',
|
||||||
|
'topics.edit.placeholder': 'Enter new name',
|
||||||
|
'topics.delete.all.title': 'Delete all topics',
|
||||||
|
'topics.delete.all.content': 'Are you sure you want to delete all topics?',
|
||||||
|
'input.new_chat': ' New Chat ',
|
||||||
|
'input.topics': ' Topics ',
|
||||||
|
'input.clear': 'Clear',
|
||||||
|
'input.expand': 'Expand',
|
||||||
|
'input.collapse': 'Collapse',
|
||||||
|
'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'
|
||||||
|
},
|
||||||
|
apps: {
|
||||||
|
title: 'Agents'
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
openai: 'OpenAI',
|
||||||
|
deepseek: 'DeepSeek',
|
||||||
|
moonshot: 'Moonshot',
|
||||||
|
silicon: 'SiliconFlow',
|
||||||
|
openrouter: 'OpenRouter',
|
||||||
|
yi: 'Lingyiwanwu',
|
||||||
|
zhipu: 'BigModel',
|
||||||
|
groq: 'Groq',
|
||||||
|
ollama: 'Ollama'
|
||||||
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
general: 'General',
|
general: 'General',
|
||||||
provider: 'Model Provider',
|
provider: 'Model Provider',
|
||||||
model: 'Model Settings',
|
model: 'Model Settings',
|
||||||
assistant: 'Default Assistant',
|
assistant: 'Default Assistant',
|
||||||
about: 'About'
|
about: 'About',
|
||||||
|
'general.title': 'General Settings',
|
||||||
|
'provider.api_key': 'API Key',
|
||||||
|
'provider.check': 'Check',
|
||||||
|
'provider.get_api_key': 'Get API Key',
|
||||||
|
'provider.api_host': 'API Host',
|
||||||
|
'provider.docs_check': 'Check',
|
||||||
|
'provider.docs_more_details': 'for more details',
|
||||||
|
'provider.search_placeholder': 'Search model id or name',
|
||||||
|
'models.default_assistant_model': 'Default Assistant Model',
|
||||||
|
'models.topic_naming_model': 'Topic Naming Model',
|
||||||
|
'models.add.add_model': 'Add Model',
|
||||||
|
'models.add.provider_name.placeholder': 'Provider Name',
|
||||||
|
'models.add.model_id.placeholder': 'Required e.g. gpt-3.5-turbo',
|
||||||
|
'models.add.model_id': 'Model ID',
|
||||||
|
'models.add.model_id.tooltip': 'Example: gpt-3.5-turbo',
|
||||||
|
'models.add.model_name': 'Model Name',
|
||||||
|
'models.add.model_name.placeholder': 'Optional e.g. GPT-4',
|
||||||
|
'models.add.group_name': 'Group Name',
|
||||||
|
'models.add.group_name.tooltip': 'Optional e.g. ChatGPT',
|
||||||
|
'models.add.group_name.placeholder': 'Optional e.g. ChatGPT',
|
||||||
|
'assistant.title': 'Default Assistant',
|
||||||
|
'about.description': 'A powerful AI assistant for producer'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'zh-CN': {
|
'zh-CN': {
|
||||||
translation: {
|
translation: {
|
||||||
|
common: {
|
||||||
|
avatar: '头像',
|
||||||
|
language: '语言',
|
||||||
|
model: '模型',
|
||||||
|
models: '模型',
|
||||||
|
topics: '话题',
|
||||||
|
docs: '文档',
|
||||||
|
and: '和',
|
||||||
|
assistant: '智能体',
|
||||||
|
name: '名称',
|
||||||
|
description: '描述',
|
||||||
|
prompt: '提示词',
|
||||||
|
rename: '重命名',
|
||||||
|
delete: '删除',
|
||||||
|
edit: '编辑',
|
||||||
|
duplicate: '复制',
|
||||||
|
copy: '复制',
|
||||||
|
regenerate: '重新生成',
|
||||||
|
provider: '提供商'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
add: '添加',
|
||||||
|
manage: '管理',
|
||||||
|
select_model: '选择模型'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
copied: '已复制!',
|
||||||
|
'assistant.added.content': '智能体添加成功',
|
||||||
|
'message.delete.title': '删除消息',
|
||||||
|
'message.delete.content': '确定要删除此消息吗?',
|
||||||
|
'error.enter.api.key': '请输入您的 API 密钥',
|
||||||
|
'error.enter.api.host': '请输入您的 API 地址',
|
||||||
|
'error.enter.model': '请选择一个模型',
|
||||||
|
'api.connection.failed': '连接失败',
|
||||||
|
'api.connection.successful': '连接成功'
|
||||||
|
},
|
||||||
|
assistant: {
|
||||||
|
'default.name': '默认助手',
|
||||||
|
'default.description': '你可以随时随地和我聊天',
|
||||||
|
'default.topic.name': '默认话题',
|
||||||
|
'topics.title': '话题',
|
||||||
|
'topics.hide_topics': '隐藏话题',
|
||||||
|
'topics.show_topics': '显示话题',
|
||||||
|
'topics.auto_rename': 'AI 重命名',
|
||||||
|
'topics.edit.title': '重命名',
|
||||||
|
'topics.edit.placeholder': '输入新名称',
|
||||||
|
'topics.delete.all.title': '删除所有话题',
|
||||||
|
'topics.delete.all.content': '确定要删除所有话题吗?',
|
||||||
|
'input.new_chat': ' 新聊天 ',
|
||||||
|
'input.topics': ' 话题 ',
|
||||||
|
'input.clear': '清除',
|
||||||
|
'input.expand': '展开',
|
||||||
|
'input.collapse': '收起',
|
||||||
|
'input.clear.title': '清除所有消息?',
|
||||||
|
'input.clear.content': '确定要清除所有消息吗?',
|
||||||
|
'input.placeholder': '在这里输入消息...',
|
||||||
|
'input.send': '发送'
|
||||||
|
},
|
||||||
|
apps: {
|
||||||
|
title: '智能体'
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
openai: 'OpenAI',
|
||||||
|
deepseek: '深度求索',
|
||||||
|
moonshot: '月之暗面',
|
||||||
|
silicon: '硅基流动',
|
||||||
|
openrouter: 'OpenRouter',
|
||||||
|
yi: '零一万物',
|
||||||
|
zhipu: '智谱AI',
|
||||||
|
groq: 'Groq',
|
||||||
|
ollama: 'Ollama'
|
||||||
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: '设置',
|
title: '设置',
|
||||||
general: '常规',
|
general: '常规',
|
||||||
provider: '模型提供商',
|
provider: '模型提供商',
|
||||||
model: '模型设置',
|
model: '模型设置',
|
||||||
assistant: '默认助手',
|
assistant: '默认助手',
|
||||||
about: '关于'
|
about: '关于',
|
||||||
|
'general.title': '常规设置',
|
||||||
|
'provider.api_key': 'API 密钥',
|
||||||
|
'provider.check': '检查',
|
||||||
|
'provider.get_api_key': '点击这里获取密钥',
|
||||||
|
'provider.api_host': 'API 地址',
|
||||||
|
'provider.docs_check': '查看',
|
||||||
|
'provider.docs_more_details': '获取更多详情',
|
||||||
|
'provider.search_placeholder': '搜索模型 ID 或名称',
|
||||||
|
'models.default_assistant_model': '默认助手模型',
|
||||||
|
'models.topic_naming_model': '话题命名模型',
|
||||||
|
'models.add.add_model': '添加模型',
|
||||||
|
'models.add.provider_name.placeholder': '必填 例如 OpenAI',
|
||||||
|
'models.add.model_id.placeholder': '必填 例如 gpt-3.5-turbo',
|
||||||
|
'models.add.model_id': '模型 ID',
|
||||||
|
'models.add.model_id.tooltip': '例如 gpt-3.5-turbo',
|
||||||
|
'models.add.model_name': '模型名称',
|
||||||
|
'models.add.model_name.placeholder': '例如 GPT-3.5',
|
||||||
|
'models.add.group_name': '分组名称',
|
||||||
|
'models.add.group_name.tooltip': '例如 ChatGPT',
|
||||||
|
'models.add.group_name.placeholder': '例如 ChatGPT',
|
||||||
|
'assistant.title': '默认助手',
|
||||||
|
'about.description': '一个为创造者而生的 AI 助手'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,7 +219,7 @@ const resources = {
|
|||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
resources,
|
resources,
|
||||||
lng: 'en-US',
|
lng: store.getState().settings.language || 'en-US',
|
||||||
fallbackLng: 'en-US',
|
fallbackLng: 'en-US',
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false
|
escapeValue: false
|
||||||
|
|||||||
@ -9,12 +9,14 @@ import { SystemAssistant } from '@renderer/types'
|
|||||||
import { getDefaultAssistant } from '@renderer/services/assistant'
|
import { getDefaultAssistant } from '@renderer/services/assistant'
|
||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
import { colorPrimary } from '@renderer/config/antd'
|
import { colorPrimary } from '@renderer/config/antd'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const { Title } = Typography
|
const { Title } = Typography
|
||||||
|
|
||||||
const AppsPage: FC = () => {
|
const AppsPage: FC = () => {
|
||||||
const { assistants, addAssistant } = useAssistants()
|
const { assistants, addAssistant } = useAssistants()
|
||||||
const assistantGroups = groupBy(SYSTEM_ASSISTANTS, 'group')
|
const assistantGroups = groupBy(SYSTEM_ASSISTANTS, 'group')
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onAddAssistant = (assistant: SystemAssistant) => {
|
const onAddAssistant = (assistant: SystemAssistant) => {
|
||||||
addAssistant({
|
addAssistant({
|
||||||
@ -22,7 +24,7 @@ const AppsPage: FC = () => {
|
|||||||
...assistant
|
...assistant
|
||||||
})
|
})
|
||||||
window.message.success({
|
window.message.success({
|
||||||
content: 'Assistant added successfully',
|
content: t('message.assistant.added.content'),
|
||||||
key: 'assistant-added',
|
key: 'assistant-added',
|
||||||
style: { marginTop: '5vh' }
|
style: { marginTop: '5vh' }
|
||||||
})
|
})
|
||||||
@ -31,7 +33,7 @@ const AppsPage: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<NavbarCenter style={{ borderRight: 'none' }}>Assistant Market</NavbarCenter>
|
<NavbarCenter style={{ borderRight: 'none' }}>{t('apps.title')}</NavbarCenter>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
{Object.keys(assistantGroups).map((group) => (
|
{Object.keys(assistantGroups).map((group) => (
|
||||||
|
|||||||
@ -8,12 +8,14 @@ import { uuid } from '@renderer/utils'
|
|||||||
import { useShowRightSidebar } from '@renderer/hooks/useStore'
|
import { useShowRightSidebar } from '@renderer/hooks/useStore'
|
||||||
import { Tooltip } from 'antd'
|
import { Tooltip } from 'antd'
|
||||||
import Navigation from './components/Navigation'
|
import Navigation from './components/Navigation'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const HomePage: FC = () => {
|
const HomePage: FC = () => {
|
||||||
const { assistants, addAssistant } = useAssistants()
|
const { assistants, addAssistant } = useAssistants()
|
||||||
const [activeAssistant, setActiveAssistant] = useState(assistants[0])
|
const [activeAssistant, setActiveAssistant] = useState(assistants[0])
|
||||||
const { showRightSidebar, setShowRightSidebar } = useShowRightSidebar()
|
const { showRightSidebar, setShowRightSidebar } = useShowRightSidebar()
|
||||||
const { defaultAssistant } = useDefaultAssistant()
|
const { defaultAssistant } = useDefaultAssistant()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onCreateAssistant = () => {
|
const onCreateAssistant = () => {
|
||||||
const assistant = { ...defaultAssistant, id: uuid() }
|
const assistant = { ...defaultAssistant, id: uuid() }
|
||||||
@ -31,7 +33,10 @@ const HomePage: FC = () => {
|
|||||||
</NavbarLeft>
|
</NavbarLeft>
|
||||||
<Navigation activeAssistant={activeAssistant} />
|
<Navigation activeAssistant={activeAssistant} />
|
||||||
<NavbarRight style={{ justifyContent: 'flex-end', padding: 5 }}>
|
<NavbarRight style={{ justifyContent: 'flex-end', padding: 5 }}>
|
||||||
<Tooltip placement="left" title={showRightSidebar ? 'Hide Topics' : 'Show Topics'} arrow>
|
<Tooltip
|
||||||
|
placement="left"
|
||||||
|
title={showRightSidebar ? t('assistant.topics.hide_topics') : t('assistant.topics.show_topics')}
|
||||||
|
arrow>
|
||||||
<NewButton onClick={setShowRightSidebar}>
|
<NewButton onClick={setShowRightSidebar}>
|
||||||
<i className={`iconfont ${showRightSidebar ? 'icon-showsidebarhoriz' : 'icon-hidesidebarhoriz'}`} />
|
<i className={`iconfont ${showRightSidebar ? 'icon-showsidebarhoriz' : 'icon-hidesidebarhoriz'}`} />
|
||||||
</NewButton>
|
</NewButton>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { droppableReorder, uuid } from '@renderer/utils'
|
|||||||
import { Dropdown, MenuProps } from 'antd'
|
import { Dropdown, MenuProps } from 'antd'
|
||||||
import { last } from 'lodash'
|
import { last } from 'lodash'
|
||||||
import { FC, useRef } from 'react'
|
import { FC, useRef } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -20,6 +21,8 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
|||||||
const { assistants, removeAssistant, updateAssistant, addAssistant, updateAssistants } = useAssistants()
|
const { assistants, removeAssistant, updateAssistant, addAssistant, updateAssistants } = useAssistants()
|
||||||
const targetAssistant = useRef<Assistant | null>(null)
|
const targetAssistant = useRef<Assistant | null>(null)
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onDelete = (assistant: Assistant) => {
|
const onDelete = (assistant: Assistant) => {
|
||||||
removeAssistant(assistant.id)
|
removeAssistant(assistant.id)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -30,7 +33,7 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
|||||||
|
|
||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: t('common.edit'),
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
icon: <EditOutlined />,
|
icon: <EditOutlined />,
|
||||||
async onClick() {
|
async onClick() {
|
||||||
@ -41,7 +44,7 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Duplicate',
|
label: t('common.duplicate'),
|
||||||
key: 'duplicate',
|
key: 'duplicate',
|
||||||
icon: <CopyOutlined />,
|
icon: <CopyOutlined />,
|
||||||
async onClick() {
|
async onClick() {
|
||||||
@ -52,7 +55,7 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
|||||||
},
|
},
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
label: 'Delete',
|
label: t('common.delete'),
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
icon: <DeleteOutlined />,
|
icon: <DeleteOutlined />,
|
||||||
danger: true,
|
danger: true,
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
import { FC } from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
const ChatSettings: FC = () => {
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<p>Chat Settings</p>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
width: 300px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default ChatSettings
|
|
||||||
@ -3,6 +3,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
|||||||
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { CopyOutlined } from '@ant-design/icons'
|
import { CopyOutlined } from '@ant-design/icons'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface CodeBlockProps {
|
interface CodeBlockProps {
|
||||||
children: string
|
children: string
|
||||||
@ -13,9 +14,11 @@ interface CodeBlockProps {
|
|||||||
const CodeBlock: React.FC<CodeBlockProps> = ({ children, className, ...rest }) => {
|
const CodeBlock: React.FC<CodeBlockProps> = ({ children, className, ...rest }) => {
|
||||||
const match = /language-(\w+)/.exec(className || '')
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onCopy = () => {
|
const onCopy = () => {
|
||||||
navigator.clipboard.writeText(children)
|
navigator.clipboard.writeText(children)
|
||||||
window.message.success({ content: 'Copied!', key: 'copy-code' })
|
window.message.success({ content: t('message.copied'), key: 'copy-code' })
|
||||||
}
|
}
|
||||||
|
|
||||||
return match ? (
|
return match ? (
|
||||||
|
|||||||
@ -20,6 +20,8 @@ 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 { useAppSelector } from '@renderer/store'
|
||||||
|
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@ -35,6 +37,8 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
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 sendMessage = () => {
|
const sendMessage = () => {
|
||||||
if (generating) {
|
if (generating) {
|
||||||
return
|
return
|
||||||
@ -75,11 +79,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addNewTopic = useCallback(() => {
|
const addNewTopic = useCallback(() => {
|
||||||
const topic: Topic = {
|
const topic: Topic = getDefaultTopic()
|
||||||
id: uuid(),
|
|
||||||
name: 'Default Topic',
|
|
||||||
messages: []
|
|
||||||
}
|
|
||||||
addTopic(topic)
|
addTopic(topic)
|
||||||
setActiveTopic(topic)
|
setActiveTopic(topic)
|
||||||
}, [addTopic, setActiveTopic])
|
}, [addTopic, setActiveTopic])
|
||||||
@ -116,21 +116,21 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
<Container id="inputbar" style={{ minHeight: expended ? '35%' : 'var(--input-bar-height)' }}>
|
<Container id="inputbar" style={{ minHeight: expended ? '35%' : 'var(--input-bar-height)' }}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<ToolbarMenu>
|
<ToolbarMenu>
|
||||||
<Tooltip placement="top" title=" New Chat " arrow>
|
<Tooltip placement="top" title={t('assistant.input.new_chat')} arrow>
|
||||||
<ToolbarButton type="text" onClick={addNewTopic}>
|
<ToolbarButton type="text" onClick={addNewTopic}>
|
||||||
<PlusCircleOutlined />
|
<PlusCircleOutlined />
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip placement="top" title=" Topics " arrow>
|
<Tooltip placement="top" title={t('assistant.input.topics')} arrow>
|
||||||
<ToolbarButton type="text" onClick={setShowRightSidebar}>
|
<ToolbarButton type="text" onClick={setShowRightSidebar}>
|
||||||
<HistoryOutlined />
|
<HistoryOutlined />
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip placement="top" title=" Clear " arrow>
|
<Tooltip placement="top" title={t('assistant.input.clear')} arrow>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
icon={false}
|
icon={false}
|
||||||
title="Clear all messages?"
|
title={t('assistant.input.clear.title')}
|
||||||
description="Are you sure to clear all messages?"
|
description={t('assistant.input.clear.content')}
|
||||||
placement="top"
|
placement="top"
|
||||||
onConfirm={clearTopic}
|
onConfirm={clearTopic}
|
||||||
okText="Clear"
|
okText="Clear"
|
||||||
@ -140,7 +140,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip placement="top" title=" Expand " arrow>
|
<Tooltip placement="top" title={expended ? t('assistant.input.collapse') : t('assistant.input.expand')} arrow>
|
||||||
<ToolbarButton type="text" onClick={() => setExpend(!expended)}>
|
<ToolbarButton type="text" onClick={() => setExpend(!expended)}>
|
||||||
{expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
{expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
@ -158,12 +158,11 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
value={text}
|
value={text}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onChange={(e) => setText(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Type your message here..."
|
placeholder={t('assistant.input.placeholder')}
|
||||||
autoFocus
|
autoFocus
|
||||||
contextMenu="true"
|
contextMenu="true"
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
styles={{ textarea: { paddingLeft: 0 } }}
|
styles={{ textarea: { paddingLeft: 0 } }}
|
||||||
allowClear
|
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { getModelLogo } from '@renderer/services/provider'
|
|||||||
import Logo from '@renderer/assets/images/logo.png'
|
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'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: Message
|
message: Message
|
||||||
@ -22,21 +23,22 @@ interface Props {
|
|||||||
|
|
||||||
const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) => {
|
const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) => {
|
||||||
const avatar = useAvatar()
|
const avatar = useAvatar()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const isLastMessage = index === 0
|
const isLastMessage = index === 0
|
||||||
const canRegenerate = isLastMessage && message.role === 'assistant'
|
const canRegenerate = isLastMessage && message.role === 'assistant'
|
||||||
|
|
||||||
const onCopy = () => {
|
const onCopy = () => {
|
||||||
navigator.clipboard.writeText(message.content)
|
navigator.clipboard.writeText(message.content)
|
||||||
window.message.success({ content: 'Copied!', key: 'copy-message' })
|
window.message.success({ content: t('message.copied'), key: 'copy-message' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDelete = async () => {
|
const onDelete = async () => {
|
||||||
const confirmed = await window.modal.confirm({
|
const confirmed = await window.modal.confirm({
|
||||||
icon: null,
|
icon: null,
|
||||||
title: 'Delete Message',
|
title: t('message.message.delete.title'),
|
||||||
content: 'Are you sure you want to delete this message?',
|
content: t('message.message.delete.content'),
|
||||||
okText: 'Delete',
|
okText: t('common.delete'),
|
||||||
okType: 'danger'
|
okType: 'danger'
|
||||||
})
|
})
|
||||||
confirmed && onDeleteMessage?.(message)
|
confirmed && onDeleteMessage?.(message)
|
||||||
@ -80,14 +82,14 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
|||||||
<EditOutlined onClick={onEdit} />
|
<EditOutlined onClick={onEdit} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip title="Copy" mouseEnterDelay={0.8}>
|
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
|
||||||
<CopyOutlined onClick={onCopy} />
|
<CopyOutlined onClick={onCopy} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Delete" mouseEnterDelay={0.8}>
|
<Tooltip title={t('common.delete')} mouseEnterDelay={0.8}>
|
||||||
<DeleteOutlined onClick={onDelete} />
|
<DeleteOutlined onClick={onDelete} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{canRegenerate && (
|
{canRegenerate && (
|
||||||
<Tooltip title="Regenerate" mouseEnterDelay={0.8}>
|
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||||
<SyncOutlined onClick={onRegenerate} />
|
<SyncOutlined onClick={onRegenerate} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -7,10 +7,10 @@ import MessageItem from './Message'
|
|||||||
import { reverse } from 'lodash'
|
import { reverse } from 'lodash'
|
||||||
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { DEFAULT_TOPIC_NAME } from '@renderer/config/constant'
|
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { runAsyncFunction } from '@renderer/utils'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
import LocalStorage from '@renderer/services/storage'
|
||||||
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
||||||
|
import { t } from 'i18next'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@ -47,7 +47,7 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const autoRenameTopic = useCallback(async () => {
|
const autoRenameTopic = useCallback(async () => {
|
||||||
if (topic.name === DEFAULT_TOPIC_NAME && messages.length >= 2) {
|
if (topic.name === t('assistant.default.topic.name') && messages.length >= 2) {
|
||||||
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
||||||
summaryText && updateTopic({ ...topic, name: summaryText })
|
summaryText && updateTopic({ ...topic, name: summaryText })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useProviders } from '@renderer/hooks/useProvider'
|
|||||||
import { Assistant } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
import { Button, Dropdown, MenuProps } from 'antd'
|
import { Button, Dropdown, MenuProps } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -14,12 +15,13 @@ interface Props {
|
|||||||
const Navigation: FC<Props> = ({ activeAssistant }) => {
|
const Navigation: FC<Props> = ({ activeAssistant }) => {
|
||||||
const { providers } = useProviders()
|
const { providers } = useProviders()
|
||||||
const { model, setModel } = useAssistant(activeAssistant.id)
|
const { model, setModel } = useAssistant(activeAssistant.id)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const items: MenuProps['items'] = providers
|
const items: MenuProps['items'] = providers
|
||||||
.filter((p) => p.models.length > 0)
|
.filter((p) => p.models.length > 0)
|
||||||
.map((p) => ({
|
.map((p) => ({
|
||||||
key: p.id,
|
key: p.id,
|
||||||
label: p.name,
|
label: t(`provider.${p.id}`),
|
||||||
type: 'group',
|
type: 'group',
|
||||||
children: p.models.map((m) => ({
|
children: p.models.map((m) => ({
|
||||||
key: m.id,
|
key: m.id,
|
||||||
@ -34,7 +36,7 @@ const Navigation: FC<Props> = ({ activeAssistant }) => {
|
|||||||
{activeAssistant?.name}
|
{activeAssistant?.name}
|
||||||
<DropdownMenu menu={{ items, style: { maxHeight: '80vh', overflow: 'auto' } }} trigger={['click']}>
|
<DropdownMenu menu={{ items, style: { maxHeight: '80vh', overflow: 'auto' } }} trigger={['click']}>
|
||||||
<Button size="small" type="primary" ghost style={{ fontSize: '11px' }}>
|
<Button size="small" type="primary" ghost style={{ fontSize: '11px' }}>
|
||||||
{model ? model.name : 'Select Model'}
|
{model ? model.name : t('button.select_model')}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</NavbarCenter>
|
</NavbarCenter>
|
||||||
|
|||||||
@ -2,21 +2,23 @@ import { useSettings } from '@renderer/hooks/useSettings'
|
|||||||
import { Dropdown, MenuProps } from 'antd'
|
import { Dropdown, MenuProps } from 'antd'
|
||||||
import { FC, PropsWithChildren } from 'react'
|
import { FC, PropsWithChildren } from 'react'
|
||||||
import { ArrowUpOutlined, EnterOutlined } from '@ant-design/icons'
|
import { ArrowUpOutlined, EnterOutlined } from '@ant-design/icons'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface Props extends PropsWithChildren {}
|
interface Props extends PropsWithChildren {}
|
||||||
|
|
||||||
const SendMessageSetting: FC<Props> = ({ children }) => {
|
const SendMessageSetting: FC<Props> = ({ children }) => {
|
||||||
const { sendMessageShortcut, setSendMessageShortcut } = useSettings()
|
const { sendMessageShortcut, setSendMessageShortcut } = useSettings()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const sendSettingItems: MenuProps['items'] = [
|
const sendSettingItems: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
label: 'Enter Send',
|
label: `Enter ${t('assistant.input.send')}`,
|
||||||
key: 'Enter',
|
key: 'Enter',
|
||||||
icon: <EnterOutlined />,
|
icon: <EnterOutlined />,
|
||||||
onClick: () => setSendMessageShortcut('Enter')
|
onClick: () => setSendMessageShortcut('Enter')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Shift + Enter Send',
|
label: `Shift+Enter ${t('assistant.input.send')}`,
|
||||||
key: 'Shift+Enter',
|
key: 'Shift+Enter',
|
||||||
icon: <ArrowUpOutlined />,
|
icon: <ArrowUpOutlined />,
|
||||||
onClick: () => setSendMessageShortcut('Shift+Enter')
|
onClick: () => setSendMessageShortcut('Shift+Enter')
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/ico
|
|||||||
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'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@ -21,10 +22,11 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
|||||||
const { showRightSidebar } = useShowRightSidebar()
|
const { showRightSidebar } = useShowRightSidebar()
|
||||||
const { removeTopic, updateTopic, removeAllTopics, updateTopics } = useAssistant(assistant.id)
|
const { removeTopic, updateTopic, removeAllTopics, updateTopics } = useAssistant(assistant.id)
|
||||||
const currentTopic = useRef<Topic | null>(null)
|
const currentTopic = useRef<Topic | null>(null)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const topicMenuItems: MenuProps['items'] = [
|
const topicMenuItems: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
label: 'Auto Rename',
|
label: t('assistant.topics.auto_rename'),
|
||||||
key: 'auto-rename',
|
key: 'auto-rename',
|
||||||
icon: <SignatureOutlined />,
|
icon: <SignatureOutlined />,
|
||||||
async onClick() {
|
async onClick() {
|
||||||
@ -40,13 +42,13 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Rename',
|
label: t('common.rename'),
|
||||||
key: 'rename',
|
key: 'rename',
|
||||||
icon: <EditOutlined />,
|
icon: <EditOutlined />,
|
||||||
async onClick() {
|
async onClick() {
|
||||||
const name = await PromptPopup.show({
|
const name = await PromptPopup.show({
|
||||||
title: 'Rename Topic',
|
title: t('assistant.topics.edit.title'),
|
||||||
message: 'Please enter the new name',
|
message: t('assistant.topics.edit.placeholder'),
|
||||||
defaultValue: currentTopic.current?.name || ''
|
defaultValue: currentTopic.current?.name || ''
|
||||||
})
|
})
|
||||||
if (name && currentTopic.current && currentTopic.current?.name !== name) {
|
if (name && currentTopic.current && currentTopic.current?.name !== name) {
|
||||||
@ -59,7 +61,7 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
|||||||
if (assistant.topics.length > 1) {
|
if (assistant.topics.length > 1) {
|
||||||
topicMenuItems.push({ type: 'divider' })
|
topicMenuItems.push({ type: 'divider' })
|
||||||
topicMenuItems.push({
|
topicMenuItems.push({
|
||||||
label: 'Delete',
|
label: t('common.delete'),
|
||||||
danger: true,
|
danger: true,
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
icon: <DeleteOutlined />,
|
icon: <DeleteOutlined />,
|
||||||
@ -87,11 +89,13 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
|||||||
return (
|
return (
|
||||||
<Container className={showRightSidebar ? '' : 'collapsed'}>
|
<Container className={showRightSidebar ? '' : 'collapsed'}>
|
||||||
<TopicTitle>
|
<TopicTitle>
|
||||||
<span>Topics ({assistant.topics.length})</span>
|
<span>
|
||||||
|
{t('assistant.topics.title')} ({assistant.topics.length})
|
||||||
|
</span>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
icon={false}
|
icon={false}
|
||||||
title="Delete all topic?"
|
title={t('assistant.topics.delete.all.title')}
|
||||||
description="Are you sure to delete all topics?"
|
description={t('assistant.topics.delete.all.content')}
|
||||||
placement="leftBottom"
|
placement="leftBottom"
|
||||||
onConfirm={removeAllTopics}
|
onConfirm={removeAllTopics}
|
||||||
okText="Delete All"
|
okText="Delete All"
|
||||||
|
|||||||
@ -3,9 +3,11 @@ 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'
|
||||||
|
|
||||||
const AboutSettings: FC = () => {
|
const AboutSettings: FC = () => {
|
||||||
const [version, setVersion] = useState('')
|
const [version, setVersion] = useState('')
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
@ -20,7 +22,7 @@ const AboutSettings: FC = () => {
|
|||||||
<Title>
|
<Title>
|
||||||
Cherry Studio <Version>(v{version})</Version>
|
Cherry Studio <Version>(v{version})</Version>
|
||||||
</Title>
|
</Title>
|
||||||
<Description>A powerful AI assistant for producer.</Description>
|
<Description>{t('settings.about.description')}</Description>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,31 +3,34 @@ import { SettingContainer, SettingDivider, SettingSubtitle, SettingTitle } from
|
|||||||
import { Input } from 'antd'
|
import { Input } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
import { useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const AssistantSettings: FC = () => {
|
const AssistantSettings: FC = () => {
|
||||||
const { defaultAssistant, updateDefaultAssistant } = useDefaultAssistant()
|
const { defaultAssistant, updateDefaultAssistant } = useDefaultAssistant()
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
<SettingTitle>Default Assistant</SettingTitle>
|
<SettingTitle>{t('settings.assistant.title')}</SettingTitle>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SettingSubtitle style={{ marginTop: 0 }}>Name</SettingSubtitle>
|
<SettingSubtitle style={{ marginTop: 0 }}>{t('common.name')}</SettingSubtitle>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Assistant Name"
|
placeholder={t('common.assistant') + t('common.name')}
|
||||||
value={defaultAssistant.name}
|
value={defaultAssistant.name}
|
||||||
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, name: e.target.value })}
|
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, name: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<SettingSubtitle>Description</SettingSubtitle>
|
<SettingSubtitle>{t('common.description')}</SettingSubtitle>
|
||||||
<TextArea
|
<TextArea
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder="Assistant Description"
|
placeholder={t('common.assistant') + t('common.description')}
|
||||||
value={defaultAssistant.description}
|
value={defaultAssistant.description}
|
||||||
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, description: e.target.value })}
|
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, description: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<SettingSubtitle>Prompt</SettingSubtitle>
|
<SettingSubtitle>{t('common.prompt')}</SettingSubtitle>
|
||||||
<TextArea
|
<TextArea
|
||||||
rows={4}
|
rows={4}
|
||||||
placeholder="Assistant Prompt"
|
placeholder={t('common.assistant') + t('common.prompt')}
|
||||||
value={defaultAssistant.prompt}
|
value={defaultAssistant.prompt}
|
||||||
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, prompt: e.target.value })}
|
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, prompt: e.target.value })}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,25 +1,37 @@
|
|||||||
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, Upload } from 'antd'
|
import { Avatar, message, 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'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
import useAvatar from '@renderer/hooks/useAvatar'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setAvatar } from '@renderer/store/runtime'
|
import { setAvatar } from '@renderer/store/runtime'
|
||||||
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
|
import { setLanguage } from '@renderer/store/settings'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
|
||||||
const GeneralSettings: FC = () => {
|
const GeneralSettings: FC = () => {
|
||||||
const avatar = useAvatar()
|
const avatar = useAvatar()
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
|
const { language } = useSettings()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const onSelectLanguage = (value: string) => {
|
||||||
|
dispatch(setLanguage(value))
|
||||||
|
i18next.changeLanguage(value)
|
||||||
|
// window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
<SettingTitle>General Settings</SettingTitle>
|
<SettingTitle>{t('settings.general.title')}</SettingTitle>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitle>Avatar</SettingRowTitle>
|
<SettingRowTitle>{t('common.avatar')}</SettingRowTitle>
|
||||||
<Upload
|
<Upload
|
||||||
customRequest={() => {}}
|
customRequest={() => {}}
|
||||||
accept="image/png, image/jpeg"
|
accept="image/png, image/jpeg"
|
||||||
@ -42,6 +54,19 @@ const GeneralSettings: FC = () => {
|
|||||||
</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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,16 +5,18 @@ import { useProviders } from '@renderer/hooks/useProvider'
|
|||||||
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||||
import { find } from 'lodash'
|
import { find } from 'lodash'
|
||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const ModelSettings: FC = () => {
|
const ModelSettings: FC = () => {
|
||||||
const { defaultModel, topicNamingModel, setDefaultModel, setTopicNamingModel } = useDefaultModel()
|
const { defaultModel, topicNamingModel, setDefaultModel, setTopicNamingModel } = useDefaultModel()
|
||||||
const { providers } = useProviders()
|
const { providers } = useProviders()
|
||||||
const allModels = providers.map((p) => p.models).flat()
|
const allModels = providers.map((p) => p.models).flat()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const selectOptions = providers
|
const selectOptions = providers
|
||||||
.filter((p) => p.models.length > 0)
|
.filter((p) => p.models.length > 0)
|
||||||
.map((p) => ({
|
.map((p) => ({
|
||||||
label: p.name,
|
label: t(`provider.${p.id}`),
|
||||||
title: p.name,
|
title: p.name,
|
||||||
options: p.models.map((m) => ({
|
options: p.models.map((m) => ({
|
||||||
label: m.name,
|
label: m.name,
|
||||||
@ -24,7 +26,7 @@ const ModelSettings: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
<SettingTitle>Default Assistant Model</SettingTitle>
|
<SettingTitle>{t('settings.models.default_assistant_model')}</SettingTitle>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<Select
|
<Select
|
||||||
defaultValue={defaultModel.id}
|
defaultValue={defaultModel.id}
|
||||||
@ -33,7 +35,7 @@ const ModelSettings: FC = () => {
|
|||||||
options={selectOptions}
|
options={selectOptions}
|
||||||
/>
|
/>
|
||||||
<div style={{ height: 30 }} />
|
<div style={{ height: 30 }} />
|
||||||
<SettingTitle>Topic Naming Model</SettingTitle>
|
<SettingTitle>{t('settings.models.topic_naming_model')}</SettingTitle>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<Select
|
<Select
|
||||||
defaultValue={topicNamingModel.id}
|
defaultValue={topicNamingModel.id}
|
||||||
|
|||||||
@ -7,11 +7,13 @@ import { Avatar, Tag } from 'antd'
|
|||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import ProviderSetting from './components/ProviderSetting'
|
import ProviderSetting from './components/ProviderSetting'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const ProviderSettings: FC = () => {
|
const ProviderSettings: FC = () => {
|
||||||
const providers = useSystemProviders()
|
const providers = useSystemProviders()
|
||||||
const { updateProviders } = useProviders()
|
const { updateProviders } = useProviders()
|
||||||
const [selectedProvider, setSelectedProvider] = useState<Provider>(providers[0])
|
const [selectedProvider, setSelectedProvider] = useState<Provider>(providers[0])
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
const onDragEnd = (result: DropResult) => {
|
||||||
if (result.destination) {
|
if (result.destination) {
|
||||||
@ -38,7 +40,7 @@ const ProviderSettings: FC = () => {
|
|||||||
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
||||||
onClick={() => setSelectedProvider(provider)}>
|
onClick={() => setSelectedProvider(provider)}>
|
||||||
<Avatar src={getProviderLogo(provider.id)} size={22} />
|
<Avatar src={getProviderLogo(provider.id)} size={22} />
|
||||||
<ProviderItemName>{provider.name}</ProviderItemName>
|
<ProviderItemName>{t(`provider.${provider.id}`)}</ProviderItemName>
|
||||||
{provider.enabled && (
|
{provider.enabled && (
|
||||||
<Tag color="green" style={{ marginLeft: 'auto' }}>
|
<Tag color="green" style={{ marginLeft: 'auto' }}>
|
||||||
ON
|
ON
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { getDefaultGroupName } from '@renderer/utils'
|
|||||||
import { Button, Form, FormProps, Input, Modal } from 'antd'
|
import { Button, Form, FormProps, Input, Modal } from 'antd'
|
||||||
import { find } from 'lodash'
|
import { find } from 'lodash'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface ShowParams {
|
interface ShowParams {
|
||||||
title: string
|
title: string
|
||||||
@ -26,6 +27,7 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve }) => {
|
|||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
const { addModel, models } = useProvider(provider.id)
|
const { addModel, models } = useProvider(provider.id)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onOk = () => {
|
const onOk = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@ -73,12 +75,16 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve }) => {
|
|||||||
colon={false}
|
colon={false}
|
||||||
style={{ marginTop: 25 }}
|
style={{ marginTop: 25 }}
|
||||||
onFinish={onFinish}>
|
onFinish={onFinish}>
|
||||||
<Form.Item label="Provider" name="provider" initialValue={provider.id} rules={[{ required: true }]}>
|
<Form.Item name="provider" label={t('common.provider')} initialValue={provider.id} rules={[{ required: true }]}>
|
||||||
<Input placeholder="Provider Name" disabled />
|
<Input placeholder={t('settings.models.add.provider_name.placeholder')} disabled />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Model ID" name="id" tooltip="Example: gpt-3.5-turbo" rules={[{ required: true }]}>
|
<Form.Item
|
||||||
|
name="id"
|
||||||
|
label={t('settings.models.add.model_id')}
|
||||||
|
tooltip={t('settings.models.add.model_id.tooltip')}
|
||||||
|
rules={[{ required: true }]}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Required e.g. gpt-3.5-turbo"
|
placeholder={t('settings.models.add.model_id.placeholder')}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
form.setFieldValue('name', e.target.value.toUpperCase())
|
form.setFieldValue('name', e.target.value.toUpperCase())
|
||||||
@ -86,15 +92,18 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Model Name" tooltip="Example: GPT-3.5" name="name">
|
<Form.Item name="name" label={t('settings.models.add.model_name')} tooltip="Example: GPT-3.5">
|
||||||
<Input placeholder="Optional e.g. GPT-4" spellCheck={false} />
|
<Input placeholder={t('settings.models.add.model_name.placeholder')} spellCheck={false} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Group Name" tooltip="Example: ChatGPT" name="group">
|
<Form.Item
|
||||||
<Input placeholder="Optional e.g. OpenAI" spellCheck={false} />
|
name="group"
|
||||||
|
label={t('settings.models.add.group_name')}
|
||||||
|
tooltip={t('settings.models.add.group_name.tooltip')}>
|
||||||
|
<Input placeholder={t('settings.models.add.group_name.placeholder')} spellCheck={false} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label=" ">
|
<Form.Item label=" ">
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
Add Model
|
{t('settings.models.add.add_model')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { groupBy, isEmpty, uniqBy } from 'lodash'
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { TopView } from '../../../components/TopView'
|
import { TopView } from '../../../components/TopView'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface ShowParams {
|
interface ShowParams {
|
||||||
provider: Provider
|
provider: Provider
|
||||||
@ -26,6 +27,7 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
|||||||
const [listModels, setListModels] = useState<Model[]>([])
|
const [listModels, setListModels] = useState<Model[]>([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const systemModels = SYSTEM_MODELS[_provider.id] || []
|
const systemModels = SYSTEM_MODELS[_provider.id] || []
|
||||||
const allModels = uniqBy([...systemModels, ...listModels, ...models], 'id')
|
const allModels = uniqBy([...systemModels, ...listModels, ...models], 'id')
|
||||||
@ -83,7 +85,9 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
|||||||
const ModalHeader = () => {
|
const ModalHeader = () => {
|
||||||
return (
|
return (
|
||||||
<Flex>
|
<Flex>
|
||||||
<ModelHeaderTitle>{provider.name} Models</ModelHeaderTitle>
|
<ModelHeaderTitle>
|
||||||
|
{provider.name} {t('common.models')}
|
||||||
|
</ModelHeaderTitle>
|
||||||
{loading && <LoadingOutlined size={20} />}
|
{loading && <LoadingOutlined size={20} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
@ -103,7 +107,7 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
|||||||
header: { padding: 22, paddingBottom: 15 }
|
header: { padding: 22, paddingBottom: 15 }
|
||||||
}}>
|
}}>
|
||||||
<SearchContainer>
|
<SearchContainer>
|
||||||
<Search placeholder="Search model id or name" allowClear onSearch={setSearchText} />
|
<Search placeholder={t('settings.provider.search_placeholder')} allowClear onSearch={setSearchText} />
|
||||||
</SearchContainer>
|
</SearchContainer>
|
||||||
<ListContainer>
|
<ListContainer>
|
||||||
{Object.keys(modelGroups).map((group) => (
|
{Object.keys(modelGroups).map((group) => (
|
||||||
|
|||||||
@ -11,91 +11,20 @@ import AddModelPopup from './AddModelPopup'
|
|||||||
import EditModelsPopup from './EditModelsPopup'
|
import EditModelsPopup from './EditModelsPopup'
|
||||||
import Link from 'antd/es/typography/Link'
|
import Link from 'antd/es/typography/Link'
|
||||||
import { checkApi } from '@renderer/services/api'
|
import { checkApi } from '@renderer/services/api'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { PROVIDER_CONFIG } from '@renderer/config/provider'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
provider: Provider
|
provider: Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
const PROVIDER_CONFIG = {
|
|
||||||
openai: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://openai.com/',
|
|
||||||
apiKey: 'https://platform.openai.com/api-keys',
|
|
||||||
docs: 'https://platform.openai.com/docs',
|
|
||||||
models: 'https://platform.openai.com/docs/models'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
silicon: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://www.siliconflow.cn/',
|
|
||||||
apiKey: 'https://cloud.siliconflow.cn/account/ak',
|
|
||||||
docs: 'https://docs.siliconflow.cn/',
|
|
||||||
models: 'https://docs.siliconflow.cn/docs/model-names'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deepseek: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://deepseek.com/',
|
|
||||||
apiKey: 'https://platform.deepseek.com/api_keys',
|
|
||||||
docs: 'https://platform.deepseek.com/api-docs/',
|
|
||||||
models: 'https://platform.deepseek.com/api-docs/'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yi: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://platform.lingyiwanwu.com/',
|
|
||||||
apiKey: 'https://platform.lingyiwanwu.com/apikeys',
|
|
||||||
docs: 'https://platform.lingyiwanwu.com/docs',
|
|
||||||
models: 'https://platform.lingyiwanwu.com/docs#%E6%A8%A1%E5%9E%8B'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
zhipu: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://open.bigmodel.cn/',
|
|
||||||
apiKey: 'https://open.bigmodel.cn/usercenter/apikeys',
|
|
||||||
docs: 'https://open.bigmodel.cn/dev/howuse/introduction',
|
|
||||||
models: 'https://open.bigmodel.cn/modelcenter/square'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
moonshot: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://moonshot.ai/',
|
|
||||||
apiKey: 'https://platform.moonshot.cn/console/api-keys',
|
|
||||||
docs: 'https://platform.moonshot.cn/docs/',
|
|
||||||
models: 'https://platform.moonshot.cn/docs/intro#%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openrouter: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://openrouter.ai/',
|
|
||||||
apiKey: 'https://openrouter.ai/settings/keys',
|
|
||||||
docs: 'https://openrouter.ai/docs/quick-start',
|
|
||||||
models: 'https://openrouter.ai/docs/models'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
groq: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://groq.com/',
|
|
||||||
apiKey: 'https://console.groq.com/keys',
|
|
||||||
docs: 'https://console.groq.com/docs/quickstart',
|
|
||||||
models: 'https://console.groq.com/docs/models'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ollama: {
|
|
||||||
websites: {
|
|
||||||
official: 'https://ollama.com/',
|
|
||||||
docs: 'https://github.com/ollama/ollama/tree/main/docs',
|
|
||||||
models: 'https://ollama.com/library'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProviderSetting: FC<Props> = ({ provider }) => {
|
const ProviderSetting: FC<Props> = ({ provider }) => {
|
||||||
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)
|
||||||
const [apiChecking, setApiChecking] = useState(false)
|
const [apiChecking, setApiChecking] = useState(false)
|
||||||
const { updateProvider, models } = useProvider(provider.id)
|
const { updateProvider, models } = useProvider(provider.id)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const modelGroups = groupBy(models, 'group')
|
const modelGroups = groupBy(models, 'group')
|
||||||
|
|
||||||
@ -107,7 +36,7 @@ const ProviderSetting: FC<Props> = ({ provider }) => {
|
|||||||
const onUpdateApiKey = () => updateProvider({ ...provider, apiKey })
|
const onUpdateApiKey = () => updateProvider({ ...provider, apiKey })
|
||||||
const onUpdateApiHost = () => updateProvider({ ...provider, apiHost })
|
const onUpdateApiHost = () => updateProvider({ ...provider, apiHost })
|
||||||
const onManageModel = () => EditModelsPopup.show({ provider })
|
const onManageModel = () => EditModelsPopup.show({ provider })
|
||||||
const onAddModel = () => AddModelPopup.show({ title: 'Add Model', provider })
|
const onAddModel = () => AddModelPopup.show({ title: t('settings.models.add_model'), provider })
|
||||||
|
|
||||||
const onCheckApi = async () => {
|
const onCheckApi = async () => {
|
||||||
setApiChecking(true)
|
setApiChecking(true)
|
||||||
@ -129,7 +58,7 @@ const ProviderSetting: FC<Props> = ({ provider }) => {
|
|||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
<SettingTitle>
|
<SettingTitle>
|
||||||
<Flex align="center">
|
<Flex align="center">
|
||||||
<span>{provider.name}</span>
|
<span>{t(`provider.${provider.id}`)}</span>
|
||||||
{officialWebsite! && (
|
{officialWebsite! && (
|
||||||
<Link target="_blank" href={providerConfig.websites.official}>
|
<Link target="_blank" href={providerConfig.websites.official}>
|
||||||
<ExportOutlined style={{ marginLeft: '8px', color: 'white', fontSize: '12px' }} />
|
<ExportOutlined style={{ marginLeft: '8px', color: 'white', fontSize: '12px' }} />
|
||||||
@ -143,11 +72,11 @@ const ProviderSetting: FC<Props> = ({ provider }) => {
|
|||||||
/>
|
/>
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
<Divider style={{ width: '100%', margin: '10px 0' }} />
|
<Divider style={{ width: '100%', margin: '10px 0' }} />
|
||||||
<SettingSubtitle style={{ marginTop: 5 }}>API Key</SettingSubtitle>
|
<SettingSubtitle style={{ marginTop: 5 }}>{t('settings.provider.api_key')}</SettingSubtitle>
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
<Input
|
<Input
|
||||||
value={apiKey}
|
value={apiKey}
|
||||||
placeholder="API Key"
|
placeholder={t('settings.provider.api_key')}
|
||||||
onChange={(e) => setApiKey(e.target.value)}
|
onChange={(e) => setApiKey(e.target.value)}
|
||||||
onBlur={onUpdateApiKey}
|
onBlur={onUpdateApiKey}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
@ -156,27 +85,26 @@ const ProviderSetting: FC<Props> = ({ provider }) => {
|
|||||||
/>
|
/>
|
||||||
{!apiKeyDisabled && (
|
{!apiKeyDisabled && (
|
||||||
<Button type={apiValid ? 'primary' : 'default'} ghost={apiValid} onClick={onCheckApi}>
|
<Button type={apiValid ? 'primary' : 'default'} ghost={apiValid} onClick={onCheckApi}>
|
||||||
{apiChecking ? <LoadingOutlined spin /> : apiValid ? <CheckOutlined /> : 'Check'}
|
{apiChecking ? <LoadingOutlined spin /> : apiValid ? <CheckOutlined /> : t('settings.provider.check')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
{apiKeyWebsite && (
|
{apiKeyWebsite && (
|
||||||
<HelpTextRow>
|
<HelpTextRow>
|
||||||
<HelpText>Get API key from: </HelpText>
|
|
||||||
<HelpLink target="_blank" href={apiKeyWebsite}>
|
<HelpLink target="_blank" href={apiKeyWebsite}>
|
||||||
{provider.name}
|
{t('settings.provider.get_api_key')}
|
||||||
</HelpLink>
|
</HelpLink>
|
||||||
</HelpTextRow>
|
</HelpTextRow>
|
||||||
)}
|
)}
|
||||||
<SettingSubtitle>API Host</SettingSubtitle>
|
<SettingSubtitle>{t('settings.provider.api_host')}</SettingSubtitle>
|
||||||
<Input
|
<Input
|
||||||
value={apiHost}
|
value={apiHost}
|
||||||
placeholder="API Host"
|
placeholder={t('settings.provider.api_host')}
|
||||||
disabled={provider.isSystem}
|
disabled={provider.isSystem}
|
||||||
onChange={(e) => setApiHost(e.target.value)}
|
onChange={(e) => setApiHost(e.target.value)}
|
||||||
onBlur={onUpdateApiHost}
|
onBlur={onUpdateApiHost}
|
||||||
/>
|
/>
|
||||||
<SettingSubtitle>Models</SettingSubtitle>
|
<SettingSubtitle>{t('common.models')}</SettingSubtitle>
|
||||||
{Object.keys(modelGroups).map((group) => (
|
{Object.keys(modelGroups).map((group) => (
|
||||||
<Card key={group} type="inner" title={group} style={{ marginBottom: '10px' }} size="small">
|
<Card key={group} type="inner" title={group} style={{ marginBottom: '10px' }} size="small">
|
||||||
{modelGroups[group].map((model) => (
|
{modelGroups[group].map((model) => (
|
||||||
@ -191,23 +119,24 @@ const ProviderSetting: FC<Props> = ({ provider }) => {
|
|||||||
))}
|
))}
|
||||||
{docsWebsite && (
|
{docsWebsite && (
|
||||||
<HelpTextRow>
|
<HelpTextRow>
|
||||||
<HelpText>Check </HelpText>
|
<HelpText>{t('settings.provider.docs_check')} </HelpText>
|
||||||
<HelpLink target="_blank" href={docsWebsite}>
|
<HelpLink target="_blank" href={docsWebsite}>
|
||||||
{provider.name} Docs
|
{t(`provider.${provider.id}`)}
|
||||||
|
{t('common.docs')}
|
||||||
</HelpLink>
|
</HelpLink>
|
||||||
<HelpText>and</HelpText>
|
<HelpText>{t('common.and')}</HelpText>
|
||||||
<HelpLink target="_blank" href={modelsWebsite}>
|
<HelpLink target="_blank" href={modelsWebsite}>
|
||||||
Models
|
{t('common.models')}
|
||||||
</HelpLink>
|
</HelpLink>
|
||||||
<HelpText>for more details</HelpText>
|
<HelpText>{t('settings.provider.docs_more_details')}</HelpText>
|
||||||
</HelpTextRow>
|
</HelpTextRow>
|
||||||
)}
|
)}
|
||||||
<Flex gap={10} style={{ marginTop: '10px' }}>
|
<Flex gap={10} style={{ marginTop: '10px' }}>
|
||||||
<Button type="primary" onClick={onManageModel} icon={<EditOutlined />}>
|
<Button type="primary" onClick={onManageModel} icon={<EditOutlined />}>
|
||||||
Manage
|
{t('button.manage')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="default" onClick={onAddModel} icon={<PlusOutlined />}>
|
<Button type="default" onClick={onAddModel} icon={<PlusOutlined />}>
|
||||||
Add
|
{t('button.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export const SettingTitle = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const SettingSubtitle = styled.div`
|
export const SettingSubtitle = styled.div`
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
margin: 15px 0 10px 0;
|
margin: 15px 0 10px 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|||||||
@ -8,6 +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'
|
||||||
|
|
||||||
interface FetchChatCompletionParams {
|
interface FetchChatCompletionParams {
|
||||||
messages: Message[]
|
messages: Message[]
|
||||||
@ -121,17 +122,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: 'Please enter your API key first', key, style })
|
window.message.error({ content: t('error.enter.api.key'), key, style })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!provider.apiHost) {
|
if (!provider.apiHost) {
|
||||||
window.message.error({ content: 'Please enter your API host first', key, style })
|
window.message.error({ content: t('error.enter.api.host'), key, style })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!model) {
|
if (!model) {
|
||||||
window.message.error({ content: 'Please select a model first', key, style })
|
window.message.error({ content: t('error.enter.model'), key, style })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +156,7 @@ 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 ? 'API connection successful' : 'API connection failed ' + errorMessage
|
content: valid ? t('api.connection.successful') : t('api.connection.failed') + ' ' + errorMessage
|
||||||
})
|
})
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
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'
|
||||||
|
|
||||||
export function getDefaultAssistant(): Assistant {
|
export function getDefaultAssistant(): Assistant {
|
||||||
return {
|
return {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
name: 'Default Assistant',
|
name: i18next.t('assistant.default.name'),
|
||||||
description: "Hello, I'm Default Assistant. You can start chatting with me right away",
|
description: i18next.t('assistant.default.description'),
|
||||||
prompt: '',
|
prompt: '',
|
||||||
topics: [getDefaultTopic()]
|
topics: [getDefaultTopic()]
|
||||||
}
|
}
|
||||||
@ -15,7 +16,7 @@ export function getDefaultAssistant(): Assistant {
|
|||||||
export function getDefaultTopic(): Topic {
|
export function getDefaultTopic(): Topic {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name: 'Default Topic',
|
name: i18next.t('assistant.default.topic.name'),
|
||||||
messages: []
|
messages: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 6,
|
version: 7,
|
||||||
blacklist: ['runtime'],
|
blacklist: ['runtime'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -102,6 +102,16 @@ const migrate = createMigrate({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// @ts-ignore store type is unknown
|
||||||
|
'7': (state: RootState) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
settings: {
|
||||||
|
...state.settings,
|
||||||
|
language: navigator.language
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,13 @@ export type SendMessageShortcut = 'Enter' | 'Shift+Enter'
|
|||||||
export interface SettingsState {
|
export interface SettingsState {
|
||||||
showRightSidebar: boolean
|
showRightSidebar: boolean
|
||||||
sendMessageShortcut: SendMessageShortcut
|
sendMessageShortcut: SendMessageShortcut
|
||||||
|
language: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SettingsState = {
|
const initialState: SettingsState = {
|
||||||
showRightSidebar: true,
|
showRightSidebar: true,
|
||||||
sendMessageShortcut: 'Enter'
|
sendMessageShortcut: 'Enter',
|
||||||
|
language: navigator.language
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsSlice = createSlice({
|
const settingsSlice = createSlice({
|
||||||
@ -21,10 +23,13 @@ const settingsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
setSendMessageShortcut: (state, action: PayloadAction<SendMessageShortcut>) => {
|
setSendMessageShortcut: (state, action: PayloadAction<SendMessageShortcut>) => {
|
||||||
state.sendMessageShortcut = action.payload
|
state.sendMessageShortcut = action.payload
|
||||||
|
},
|
||||||
|
setLanguage: (state, action: PayloadAction<string>) => {
|
||||||
|
state.language = action.payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { toggleRightSidebar, setSendMessageShortcut } = settingsSlice.actions
|
export const { toggleRightSidebar, setSendMessageShortcut, setLanguage } = settingsSlice.actions
|
||||||
|
|
||||||
export default settingsSlice.reducer
|
export default settingsSlice.reducer
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user