From 392dfcee1371c56a6895770bcfc429b0b3ec8f00 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Thu, 4 Jul 2024 15:26:29 +0800 Subject: [PATCH] feat: add models --- .../src/components/Popups/ModalListPopup.tsx | 131 +++++++++++ .../src/components/Popups/TemplatePopup.tsx | 54 +++++ src/renderer/src/components/app/Sidebar.tsx | 2 +- src/renderer/src/config/assistant.ts | 144 ++++++++++++ src/renderer/src/config/models.ts | 222 ++++++++++++++++++ .../{useAssistants.ts => useAssistant.ts} | 2 +- src/renderer/src/hooks/useProvider.ts | 32 +++ src/renderer/src/pages/apps/AppsPage.tsx | 108 +++++++++ src/renderer/src/pages/home/HomePage.tsx | 2 +- .../src/pages/home/components/Assistants.tsx | 2 +- .../src/pages/home/components/Chat/Chat.tsx | 2 +- .../home/components/Chat/Conversations.tsx | 2 +- .../pages/home/components/Chat/Inputbar.tsx | 2 +- .../pages/home/components/Chat/TopicList.tsx | 2 +- .../src/pages/settings/AboutSettings.tsx | 4 +- .../src/pages/settings/CommonSettings.tsx | 5 +- .../settings/DefaultAssistantSetting.tsx | 5 +- .../src/pages/settings/DeveloperSetting.tsx | 5 +- .../pages/settings/LanguageModelsSettings.tsx | 72 ++++-- .../src/pages/settings/SettingsPage.tsx | 3 +- .../settings/SystemAssistantSettings.tsx | 5 +- .../components/ModalProviderSetting.tsx | 112 +++++++++ src/renderer/src/services/event.ts | 3 +- src/renderer/src/store/index.ts | 4 +- src/renderer/src/store/llm.ts | 83 ++++++- src/renderer/src/types/index.ts | 26 ++ 26 files changed, 978 insertions(+), 56 deletions(-) create mode 100644 src/renderer/src/components/Popups/ModalListPopup.tsx create mode 100644 src/renderer/src/components/Popups/TemplatePopup.tsx create mode 100644 src/renderer/src/config/assistant.ts create mode 100644 src/renderer/src/config/models.ts rename src/renderer/src/hooks/{useAssistants.ts => useAssistant.ts} (97%) create mode 100644 src/renderer/src/hooks/useProvider.ts create mode 100644 src/renderer/src/pages/settings/components/ModalProviderSetting.tsx diff --git a/src/renderer/src/components/Popups/ModalListPopup.tsx b/src/renderer/src/components/Popups/ModalListPopup.tsx new file mode 100644 index 00000000..783deb9b --- /dev/null +++ b/src/renderer/src/components/Popups/ModalListPopup.tsx @@ -0,0 +1,131 @@ +import { Button, Modal } from 'antd' +import { useState } from 'react' +import { TopView } from '../TopView' +import { Model, Provider } from '@renderer/types' +import { groupBy } from 'lodash' +import styled from 'styled-components' +import { MinusOutlined, PlusOutlined } from '@ant-design/icons' +import { useProvider } from '@renderer/hooks/useProvider' +import { SYSTEM_MODELS } from '@renderer/config/models' + +interface ShowParams { + provider: Provider +} + +interface Props extends ShowParams { + resolve: (data: any) => void +} + +const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { + const [open, setOpen] = useState(true) + const { provider, addModel, removeModel } = useProvider(_provider.id) + + const systemModels = SYSTEM_MODELS[_provider.id] + const systemModelGroups = groupBy(systemModels, 'group') + + const onOk = () => { + setOpen(false) + } + + const onCancel = () => { + setOpen(false) + } + + const onClose = () => { + resolve({}) + } + + const onAddModel = (model: Model) => { + addModel(model) + } + + const onRemoveModel = (model: Model) => { + removeModel(model) + } + + return ( + + + {Object.keys(systemModelGroups).map((group) => ( +
+ {group} + {systemModelGroups[group].map((model) => { + const hasModel = provider.models.find((m) => m.id === model.id) + return ( + + {model.id} + {hasModel ? ( +
+ ))} +
+
+ ) +} + +const ListContainer = styled.div` + max-height: 70vh; + overflow-y: scroll; + padding-bottom: 20px; +` + +const ListHeader = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: var(--color-background-soft); + padding: 8px 22px; + color: #ffffff50; +` + +const ListItem = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 10px 22px; +` + +const ListItemName = styled.div` + color: #fff; + font-size: 14px; + font-weight: 600; +` + +export default class ModalListPopup { + static topviewId = 0 + static hide() { + TopView.hide(this.topviewId) + } + static show(props: ShowParams) { + return new Promise((resolve) => { + this.topviewId = TopView.show( + { + resolve(v) + this.hide() + }} + /> + ) + }) + } +} diff --git a/src/renderer/src/components/Popups/TemplatePopup.tsx b/src/renderer/src/components/Popups/TemplatePopup.tsx new file mode 100644 index 00000000..10adf006 --- /dev/null +++ b/src/renderer/src/components/Popups/TemplatePopup.tsx @@ -0,0 +1,54 @@ +import { Modal } from 'antd' +import { useState } from 'react' +import { TopView } from '../TopView' +import { Box } from '../Layout' + +interface ShowParams { + title: string +} + +interface Props extends ShowParams { + resolve: (data: any) => void +} + +const PopupContainer: React.FC = ({ title, resolve }) => { + const [open, setOpen] = useState(true) + + const onOk = () => { + setOpen(false) + } + + const onCancel = () => { + setOpen(false) + } + + const onClose = () => { + resolve({}) + } + + return ( + + Name + + ) +} + +export default class TemplatePopup { + static topviewId = 0 + static hide() { + TopView.hide(this.topviewId) + } + static show(props: ShowParams) { + return new Promise((resolve) => { + this.topviewId = TopView.show( + { + resolve(v) + this.hide() + }} + /> + ) + }) + } +} diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index 127d71fb..281d129a 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -28,7 +28,7 @@ const Sidebar: FC = () => { - + diff --git a/src/renderer/src/config/assistant.ts b/src/renderer/src/config/assistant.ts new file mode 100644 index 00000000..220a476d --- /dev/null +++ b/src/renderer/src/config/assistant.ts @@ -0,0 +1,144 @@ +import { SystemAssistant } from '@renderer/types' + +export const SYSTEM_ASSISTANTS: SystemAssistant[] = [ + // Software Engineer + { + id: '43CEDACF-C9EB-431B-848C-4D08EC26EB90', + name: '软件工程师', + description: '你是一个高级软件工程师,你需要帮我解答各种技术难题', + prompt: + '你是一个高级软件工程师,你需要帮我解答各种技术难题、设计技术方案以及编写代码。你编写的代码必须可以正常运行,而且没有任何 Bug 和其他问题。', + group: 'Software Engineer' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2A', + name: '前端工程师', + description: '你是一个高级前端工程师,你需要帮我解答各种技术难题', + prompt: + '你擅长使用 TypeScript, JavaScript, HMLT, CSS 等编程语言。同时你还会使用 Node.js 及各种包来解决开发中遇到的问题。你还会使用 React, Vue 等前端框架。对于我的问题希望你能给出具体的代码示例,最好能够封装成一个函数方便我复制运行测试。', + group: 'Software Engineer' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2B', + name: '后端工程师', + description: '你是一个高级后端工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级后端工程师,你需要帮我解答各种技术难题', + group: 'Software Engineer' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2C', + name: '全栈工程师', + description: '你是一个高级全栈工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级全栈工程师,你需要帮我解答各种技术难题', + group: 'Software Engineer' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2D', + name: '测试工程师', + description: '你是一个高级测试工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级测试工程师,你需要帮我解答各种技术难题', + group: 'Software Engineer' + }, + // Programming Languages Assistants + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2E', + name: 'Python', + description: '你是一个高级Python工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级Python工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2F', + name: 'Java', + description: '你是一个高级Java工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级Java工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D30', + name: 'C#', + description: '你是一个高级C#工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级C#工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D31', + name: 'C++', + description: '你是一个高级C++工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级C++工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D32', + name: 'C', + description: '你是一个高级C工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级C工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D33', + name: 'Go', + description: '你是一个高级Go工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级Go工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D34', + name: 'Rust', + description: '你是一个高级Rust工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级Rust工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D35', + name: 'PHP', + description: '你是一个高级PHP工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级PHP工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D36', + name: 'Ruby', + description: '你是一个高级Ruby工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级Ruby工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D37', + name: 'Swift', + description: '你是一个高级Swift工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级Swift工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D38', + name: 'Kotlin', + description: '你是一个高级Kotlin工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级Kotlin工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D39', + name: 'Dart', + description: '你是一个高级Dart工程师,你需要帮我解答各种技术难题', + prompt: '你是一个高级Dart工程师,你需要帮我解答各种技术难题', + group: 'Programming Languages' + }, + // Translation + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D40', + name: '翻译成中文', + description: '你是一个好用的翻译助手, 可以把任何语言翻译成中文', + prompt: + '你是一个好用的翻译助手。请将我的英文翻译成中文,将所有非中文的翻译成中文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合中文的语言习惯。', + group: 'Translation' + }, + { + id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D41', + name: '翻译成英文', + description: '你是一个好用的翻译助手, 可以把任何语言翻译成英文', + prompt: + '你是一个好用的翻译助手。请将我的中文翻译成英文,将所有非中文的翻译成英文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合英文的语言习惯。', + group: 'Translation' + } +] diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts new file mode 100644 index 00000000..b3c06e20 --- /dev/null +++ b/src/renderer/src/config/models.ts @@ -0,0 +1,222 @@ +import { Model } from '@renderer/types' + +export const SYSTEM_MODELS: Record = { + openai: [ + { + id: 'gpt-3.5-turbo', + name: 'gpt-3.5-turbo', + group: 'GPT 3.5', + temperature: 0.7 + }, + { + id: 'gpt-3.5-turbo-0301', + name: 'gpt-3.5-turbo', + group: 'GPT 3.5', + temperature: 0.3 + }, + { + id: 'gpt-4', + name: 'gpt-4', + group: 'GPT 4', + temperature: 0.7 + }, + { + id: 'gpt-4-0314', + name: 'gpt-4', + group: 'GPT 4', + temperature: 0.3 + }, + { + id: 'gpt-4-32k', + name: 'gpt-4-32k', + group: 'GPT 4', + temperature: 0.7 + }, + { + id: 'gpt-4-32k-0314', + name: 'gpt-4-32k', + group: 'GPT 4', + temperature: 0.3 + } + ], + silicon: [ + { + id: 'deepseek-ai/DeepSeek-V2-Chat', + name: 'DeepSeek-V2-Chat', + group: 'DeepSeek', + temperature: 0.7 + }, + { + id: 'deepseek-ai/DeepSeek-Coder-V2-Instruct', + name: 'DeepSeek-Coder-V2-Instruct', + group: 'DeepSeek', + temperature: 0.7 + }, + { + id: 'deepseek-ai/deepseek-llm-67b-chat', + name: 'deepseek-llm-67b-chat', + group: 'DeepSeek', + temperature: 0.7 + }, + { + id: 'google/gemma-2-27b-it', + name: 'gemma-2-27b-it', + group: 'Gemma', + temperature: 0.7 + }, + { + id: 'google/gemma-2-9b-it', + name: 'gemma-2-9b-it', + group: 'Gemma', + temperature: 0.7 + }, + { + id: 'Qwen/Qwen2-7B-Instruct', + name: 'Qwen2-7B-Instruct', + group: 'Qwen2', + temperature: 0.7 + }, + { + id: 'Qwen/Qwen2-1.5B-Instruct', + name: 'Qwen2-1.5B-Instruct', + group: 'Qwen2', + temperature: 0.7 + }, + { + id: 'Qwen/Qwen1.5-7B-Chat', + name: 'Qwen1.5-7B-Chat', + group: 'Qwen1.5', + temperature: 0.7 + }, + { + id: 'Qwen/Qwen2-72B-Instruct', + name: 'Qwen2-72B-Instruct', + group: 'Qwen2', + temperature: 0.7 + }, + { + id: 'Qwen/Qwen2-57B-A14B-Instruct', + name: 'Qwen2-57B-A14B-Instruct', + group: 'Qwen2', + temperature: 0.7 + }, + { + id: 'Qwen/Qwen1.5-110B-Chat', + name: 'Qwen1.5-110B-Chat', + group: 'Qwen1.5', + temperature: 0.7 + }, + { + id: 'Qwen/Qwen1.5-32B-Chat', + name: 'Qwen1.5-32B-Chat', + group: 'Qwen1.5', + temperature: 0.7 + }, + { + id: 'Qwen/Qwen1.5-14B-Chat', + name: 'Qwen1.5-14B-Chat', + group: 'Qwen1.5', + temperature: 0.7 + }, + { + id: 'THUDM/glm-4-9b-chat', + name: 'glm-4-9b-chat', + group: 'GLM', + temperature: 0.7 + }, + { + id: 'THUDM/chatglm3-6b', + name: 'chatglm3-6b', + group: 'GLM', + temperature: 0.7 + }, + { + id: '01-ai/Yi-1.5-9B-Chat-16K', + name: 'Yi-1.5-9B-Chat-16K', + group: 'Yi', + temperature: 0.7 + }, + { + id: '01-ai/Yi-1.5-6B-Chat', + name: 'Yi-1.5-6B-Chat', + group: 'Yi', + temperature: 0.7 + }, + { + id: '01-ai/Yi-1.5-34B-Chat-16K', + name: 'Yi-1.5-34B-Chat-16K', + group: 'Yi', + temperature: 0.7 + }, + { + id: 'OpenAI/GPT-4o', + name: 'GPT-4o', + group: 'OpenAI', + temperature: 0.7 + }, + { + id: 'OpenAI/GPT-3.5 Turbo', + name: 'GPT-3.5 Turbo', + group: 'OpenAI', + temperature: 0.7 + }, + { + id: 'Anthropic/claude-3-5-sonnet', + name: 'claude-3-5-sonnet', + group: 'Claude', + temperature: 0.7 + }, + { + id: 'meta-llama/Meta-Llama-3-8B-Instruct', + name: 'Meta-Llama-3-8B-Instruct', + group: 'Meta Llama', + temperature: 0.7 + }, + { + id: 'meta-llama/Meta-Llama-3-70B-Instruct', + name: 'Meta-Llama-3-70B-Instruct', + group: 'Meta Llama', + temperature: 0.7 + } + ], + deepseek: [ + { + id: 'deepseek-chat', + name: 'deepseek-chat', + group: 'Deepseek Chat', + temperature: 0.7 + }, + { + id: 'deepseek-coder', + name: 'deepseek-coder', + group: 'Deepseek Coder', + temperature: 1.0 + } + ], + groq: [ + { + id: 'llama3-8b-8192', + name: 'LLaMA3 8b', + group: 'Llama3', + temperature: 0.7 + }, + { + id: 'llama3-70b-8192', + name: 'LLaMA3 70b', + group: 'Llama3', + temperature: 0.7 + }, + { + id: 'mixtral-8x7b-32768', + name: 'Mixtral 8x7b', + group: 'Mixtral', + temperature: 0.7 + }, + { + id: 'gemma-7b-it', + name: 'Gemma 7b', + group: 'Gemma', + temperature: 0.7 + } + ] +} diff --git a/src/renderer/src/hooks/useAssistants.ts b/src/renderer/src/hooks/useAssistant.ts similarity index 97% rename from src/renderer/src/hooks/useAssistants.ts rename to src/renderer/src/hooks/useAssistant.ts index d0188119..42bfa9cc 100644 --- a/src/renderer/src/hooks/useAssistants.ts +++ b/src/renderer/src/hooks/useAssistant.ts @@ -11,7 +11,7 @@ import { import { Assistant, Topic } from '@renderer/types' import localforage from 'localforage' -export default function useAssistants() { +export function useAssistants() { const { assistants } = useAppSelector((state) => state.assistants) const dispatch = useAppDispatch() diff --git a/src/renderer/src/hooks/useProvider.ts b/src/renderer/src/hooks/useProvider.ts new file mode 100644 index 00000000..0046163e --- /dev/null +++ b/src/renderer/src/hooks/useProvider.ts @@ -0,0 +1,32 @@ +import { useAppDispatch, useAppSelector } from '@renderer/store' +import { + addModel as _addModel, + removeModel as _removeModel, + updateProvider as _updateProvider +} from '@renderer/store/llm' +import { Model, Provider } from '@renderer/types' + +export function useProviders() { + return useAppSelector((state) => state.llm.providers) +} + +export function useProvider(id: string) { + const provider = useAppSelector((state) => state.llm.providers.find((p) => p.id === id) as Provider) + const dispatch = useAppDispatch() + + return { + provider, + models: provider.models, + updateProvider: (provider: Provider) => dispatch(_updateProvider(provider)), + addModel: (model: Model) => dispatch(_addModel({ providerId: id, model })), + removeModel: (model: Model) => dispatch(_removeModel({ providerId: id, model })) + } +} + +export function useDefaultProvider() { + return useAppSelector((state) => state.llm.providers.find((p) => p.isDefault)) +} + +export function useSystemProviders() { + return useAppSelector((state) => state.llm.providers.filter((p) => p.isSystem)) +} diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index 3410520a..85e5ff0b 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -1,13 +1,81 @@ import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' +import { SYSTEM_ASSISTANTS } from '@renderer/config/assistant' +import { Button, Col, message, Row, Tooltip, Typography } from 'antd' +import { find, groupBy } from 'lodash' import { FC } from 'react' import styled from 'styled-components' +import { CheckOutlined, PlusOutlined } from '@ant-design/icons' +import { SystemAssistant } from '@renderer/types' +import { getDefaultAssistant } from '@renderer/services/assistant' +import { useAssistants } from '@renderer/hooks/useAssistant' + +const { Title } = Typography const AppsPage: FC = () => { + const { assistants, addAssistant } = useAssistants() + const assistantGroups = groupBy(SYSTEM_ASSISTANTS, 'group') + const [messageApi, contextHolder] = message.useMessage() + + const onAddAssistant = (assistant: SystemAssistant) => { + addAssistant({ + ...getDefaultAssistant(), + ...assistant + }) + messageApi.destroy() + messageApi.open({ + type: 'success', + content: 'Assistant added successfully', + style: { + marginTop: '5vh' + } + }) + } + return ( + {contextHolder} Assistant Market + + {Object.keys(assistantGroups).map((group) => ( +
+ + {group} + + + {assistantGroups[group].map((assistant, index) => { + const added = find(assistants, { id: assistant.id }) + return ( + + + + + {assistant.name} + + {added &&
+ ))} +
) } @@ -15,6 +83,46 @@ const AppsPage: FC = () => { const Container = styled.div` display: flex; flex: 1; + flex-direction: column; + height: 100%; +` + +const ContentContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + height: calc(100vh - var(--navbar-height)); + padding: 20px; + overflow-y: scroll; +` + +const AssistantCard = styled.div` + margin-bottom: 16px; + background-color: #141414; + border-radius: 10px; + padding: 20px; +` + +const AssistantHeader = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +` + +const AssistantCardDescription = styled.div` + font-size: 12px; + color: #888; + margin-top: 10px; + margin-bottom: 10px; + line-height: 1.5; +` + +const AssistantCardPrompt = styled.div` + color: white; + margin-top: 10px; + margin-bottom: 10px; + line-height: 1.5; ` export default AppsPage diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index f9361d4e..f2c8bec5 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -1,5 +1,5 @@ import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar' -import useAssistants from '@renderer/hooks/useAssistants' +import { useAssistants } from '@renderer/hooks/useAssistant' import { FC, useState } from 'react' import styled from 'styled-components' import Chat from './components/Chat/Chat' diff --git a/src/renderer/src/pages/home/components/Assistants.tsx b/src/renderer/src/pages/home/components/Assistants.tsx index cf001976..307b40a8 100644 --- a/src/renderer/src/pages/home/components/Assistants.tsx +++ b/src/renderer/src/pages/home/components/Assistants.tsx @@ -1,6 +1,6 @@ import { FC, useRef } from 'react' import styled from 'styled-components' -import useAssistants from '@renderer/hooks/useAssistants' +import { useAssistants } from '@renderer/hooks/useAssistant' import { Assistant } from '@renderer/types' import { Dropdown, MenuProps } from 'antd' import { last } from 'lodash' diff --git a/src/renderer/src/pages/home/components/Chat/Chat.tsx b/src/renderer/src/pages/home/components/Chat/Chat.tsx index 2d88b5fd..a52b7c0c 100644 --- a/src/renderer/src/pages/home/components/Chat/Chat.tsx +++ b/src/renderer/src/pages/home/components/Chat/Chat.tsx @@ -5,7 +5,7 @@ import Inputbar from './Inputbar' import Conversations from './Conversations' import { Flex } from 'antd' import TopicList from './TopicList' -import { useAssistant } from '@renderer/hooks/useAssistants' +import { useAssistant } from '@renderer/hooks/useAssistant' import { useActiveTopic } from '@renderer/hooks/useTopic' interface Props { diff --git a/src/renderer/src/pages/home/components/Chat/Conversations.tsx b/src/renderer/src/pages/home/components/Chat/Conversations.tsx index 01b48f4a..33c5e960 100644 --- a/src/renderer/src/pages/home/components/Chat/Conversations.tsx +++ b/src/renderer/src/pages/home/components/Chat/Conversations.tsx @@ -7,7 +7,7 @@ import MessageItem from './Message' import { reverse } from 'lodash' import hljs from 'highlight.js' import { fetchChatCompletion, fetchConversationSummary } from '@renderer/services/api' -import { useAssistant } from '@renderer/hooks/useAssistants' +import { useAssistant } from '@renderer/hooks/useAssistant' import { DEFAULT_TOPIC_NAME } from '@renderer/config/constant' import { runAsyncFunction } from '@renderer/utils' import LocalStorage from '@renderer/services/storage' diff --git a/src/renderer/src/pages/home/components/Chat/Inputbar.tsx b/src/renderer/src/pages/home/components/Chat/Inputbar.tsx index 20b347cd..3b7bd155 100644 --- a/src/renderer/src/pages/home/components/Chat/Inputbar.tsx +++ b/src/renderer/src/pages/home/components/Chat/Inputbar.tsx @@ -6,7 +6,7 @@ import styled from 'styled-components' import { MoreOutlined } from '@ant-design/icons' import { Button, Popconfirm, Tooltip } from 'antd' import { useShowRightSidebar } from '@renderer/hooks/useStore' -import { useAssistant } from '@renderer/hooks/useAssistants' +import { useAssistant } from '@renderer/hooks/useAssistant' import { ClearOutlined, HistoryOutlined, PlusCircleOutlined } from '@ant-design/icons' interface Props { diff --git a/src/renderer/src/pages/home/components/Chat/TopicList.tsx b/src/renderer/src/pages/home/components/Chat/TopicList.tsx index f9b19dd7..9198efac 100644 --- a/src/renderer/src/pages/home/components/Chat/TopicList.tsx +++ b/src/renderer/src/pages/home/components/Chat/TopicList.tsx @@ -1,5 +1,5 @@ import PromptPopup from '@renderer/components/Popups/PromptPopup' -import { useAssistant } from '@renderer/hooks/useAssistants' +import { useAssistant } from '@renderer/hooks/useAssistant' import { useShowRightSidebar } from '@renderer/hooks/useStore' import { fetchConversationSummary } from '@renderer/services/api' import { Assistant, Topic } from '@renderer/types' diff --git a/src/renderer/src/pages/settings/AboutSettings.tsx b/src/renderer/src/pages/settings/AboutSettings.tsx index 6bf07fe6..5b405839 100644 --- a/src/renderer/src/pages/settings/AboutSettings.tsx +++ b/src/renderer/src/pages/settings/AboutSettings.tsx @@ -5,6 +5,8 @@ const AboutSettings: FC = () => { return About } -const Container = styled.div`` +const Container = styled.div` + padding: 20px; +` export default AboutSettings diff --git a/src/renderer/src/pages/settings/CommonSettings.tsx b/src/renderer/src/pages/settings/CommonSettings.tsx index 8f2c6156..6384dbc0 100644 --- a/src/renderer/src/pages/settings/CommonSettings.tsx +++ b/src/renderer/src/pages/settings/CommonSettings.tsx @@ -5,6 +5,7 @@ const CommonSettings: FC = () => { return Common Settings } -const Container = styled.div`` - +const Container = styled.div` + padding: 20px; +` export default CommonSettings diff --git a/src/renderer/src/pages/settings/DefaultAssistantSetting.tsx b/src/renderer/src/pages/settings/DefaultAssistantSetting.tsx index 32157f27..ee23caa1 100644 --- a/src/renderer/src/pages/settings/DefaultAssistantSetting.tsx +++ b/src/renderer/src/pages/settings/DefaultAssistantSetting.tsx @@ -5,6 +5,7 @@ const DefaultAssistantSetting: FC = () => { return Default Assistant } -const Container = styled.div`` - +const Container = styled.div` + padding: 20px; +` export default DefaultAssistantSetting diff --git a/src/renderer/src/pages/settings/DeveloperSetting.tsx b/src/renderer/src/pages/settings/DeveloperSetting.tsx index f4e3ca90..6c1ba5d6 100644 --- a/src/renderer/src/pages/settings/DeveloperSetting.tsx +++ b/src/renderer/src/pages/settings/DeveloperSetting.tsx @@ -5,6 +5,7 @@ const DeveloperSetting: FC = () => { return Developer } -const Container = styled.div`` - +const Container = styled.div` + padding: 20px; +` export default DeveloperSetting diff --git a/src/renderer/src/pages/settings/LanguageModelsSettings.tsx b/src/renderer/src/pages/settings/LanguageModelsSettings.tsx index 9d4704aa..ba043dd0 100644 --- a/src/renderer/src/pages/settings/LanguageModelsSettings.tsx +++ b/src/renderer/src/pages/settings/LanguageModelsSettings.tsx @@ -1,36 +1,64 @@ -import { Collapse } from 'antd' -import { FC } from 'react' +import { useSystemProviders } from '@renderer/hooks/useProvider' +import { Provider } from '@renderer/types' +import { FC, useState } from 'react' import styled from 'styled-components' +import ModalProviderSetting from './components/ModalProviderSetting' +// OpenAI Silicon deepseek Groq const LanguageModelsSettings: FC = () => { + const providers = useSystemProviders() + const [selectedProvider, setSelectedProvider] = useState(providers[0]) + return ( - - -

OpenAI

-
-
- - -

Silicon

-
-
- - -

deepseek

-
-
- - -

Groq

-
-
+ + {providers.map((provider) => ( + setSelectedProvider(provider)}> + {provider.name} + + ))} + +
) } const Container = styled.div` width: 100%; + display: flex; + flex-direction: row; +` + +const ProviderListContainer = styled.div` + display: flex; + flex-direction: column; + width: var(--assistants-width); + height: 100%; + border-right: 0.5px solid var(--color-border); + padding: 10px; +` + +const ProviderListItem = styled.div` + display: flex; + flex-direction: row; + align-items: center; + padding: 6px 10px; + margin-bottom: 5px; + width: 100%; + cursor: pointer; + border-radius: 5px; + font-size: 14px; + transition: all 0.2s ease-in-out; + &:hover { + background: #135200; + } + &.active { + background: #135200; + font-weight: bold; + } ` export default LanguageModelsSettings diff --git a/src/renderer/src/pages/settings/SettingsPage.tsx b/src/renderer/src/pages/settings/SettingsPage.tsx index 87a78a56..b06f1c6c 100644 --- a/src/renderer/src/pages/settings/SettingsPage.tsx +++ b/src/renderer/src/pages/settings/SettingsPage.tsx @@ -96,8 +96,7 @@ const SettingContent = styled.div` display: flex; height: 100%; flex: 1; - border-right: 1px solid var(--color-border); - padding: 20px; + border-right: 0.5px solid var(--color-border); ` export default SettingsPage diff --git a/src/renderer/src/pages/settings/SystemAssistantSettings.tsx b/src/renderer/src/pages/settings/SystemAssistantSettings.tsx index 231c49dd..6d7b458f 100644 --- a/src/renderer/src/pages/settings/SystemAssistantSettings.tsx +++ b/src/renderer/src/pages/settings/SystemAssistantSettings.tsx @@ -5,6 +5,7 @@ const SystemAssistantSettings: FC = () => { return System Assistant } -const Container = styled.div`` - +const Container = styled.div` + padding: 20px; +` export default SystemAssistantSettings diff --git a/src/renderer/src/pages/settings/components/ModalProviderSetting.tsx b/src/renderer/src/pages/settings/components/ModalProviderSetting.tsx new file mode 100644 index 00000000..7d8d2d68 --- /dev/null +++ b/src/renderer/src/pages/settings/components/ModalProviderSetting.tsx @@ -0,0 +1,112 @@ +import { Provider } from '@renderer/types' +import { FC, useEffect, useState } from 'react' +import styled from 'styled-components' +import { Button, Card, Divider, Input } from 'antd' +import { useProvider } from '@renderer/hooks/useProvider' +import ModalListPopup from '@renderer/components/Popups/ModalListPopup' +import { groupBy } from 'lodash' + +interface Props { + provider: Provider +} + +const ModalProviderSetting: FC = ({ provider }) => { + const [apiKey, setApiKey] = useState(provider.apiKey) + const [apiHost, setApiHost] = useState(provider.apiHost) + const [apiPath, setApiPath] = useState(provider.apiPath) + const { updateProvider, models } = useProvider(provider.id) + + const modelGroups = groupBy(models, 'group') + + useEffect(() => { + setApiKey(provider.apiKey) + setApiHost(provider.apiHost) + setApiPath(provider.apiPath) + }, [provider]) + + const onUpdateApiKey = () => { + updateProvider({ ...provider, apiKey }) + } + + const onUpdateApiHost = () => { + updateProvider({ ...provider, apiHost }) + } + + const onUpdateApiPath = () => { + updateProvider({ ...provider, apiHost }) + } + + const onAddModal = () => { + ModalListPopup.show({ provider }) + } + + return ( + + {provider.name} + + API Key + setApiKey(e.target.value)} onBlur={onUpdateApiKey} /> + API Host + setApiHost(e.target.value)} + onBlur={onUpdateApiHost} + /> + API Path + setApiPath(e.target.value)} + onBlur={onUpdateApiPath} + /> + Models + {Object.keys(modelGroups).map((group) => ( + + {modelGroups[group].map((model) => ( + {model.id} + ))} + + ))} + + + ) +} + +const Container = styled.div` + display: flex; + flex-direction: column; + flex: 1; + height: calc(100vh - var(--navbar-height)); + padding: 15px; + overflow-y: scroll; + + &::-webkit-scrollbar { + display: none; + } +` + +const Title = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +` + +const SubTitle = styled.div` + font-size: 12px; + color: var(--color-text-3); + margin: 10px 0; +` + +const ModelListItem = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 5px 0; +` + +export default ModalProviderSetting diff --git a/src/renderer/src/services/event.ts b/src/renderer/src/services/event.ts index a46f1955..d1d79e5f 100644 --- a/src/renderer/src/services/event.ts +++ b/src/renderer/src/services/event.ts @@ -6,5 +6,6 @@ export const EVENT_NAMES = { SEND_MESSAGE: 'SEND_MESSAGE', AI_CHAT_COMPLETION: 'AI_CHAT_COMPLETION', AI_AUTO_RENAME: 'AI_AUTO_RENAME', - CLEAR_CONVERSATION: 'CLEAR_CONVERSATION' + CLEAR_CONVERSATION: 'CLEAR_CONVERSATION', + ADD_ASSISTANT: 'ADD_ASSISTANT' } diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 4abdd804..1395efdb 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -4,10 +4,12 @@ import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, R import storage from 'redux-persist/lib/storage' import assistants from './assistants' import settings from './settings' +import llm from './llm' const rootReducer = combineReducers({ assistants, - settings + settings, + llm }) const store = configureStore({ diff --git a/src/renderer/src/store/llm.ts b/src/renderer/src/store/llm.ts index 9153353e..e2d133bc 100644 --- a/src/renderer/src/store/llm.ts +++ b/src/renderer/src/store/llm.ts @@ -1,31 +1,88 @@ -import { createSlice } from '@reduxjs/toolkit' - -type Provider = { - id: string - name: string - apiKey: string - apiUrl: string - url: string -} +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { Model, Provider } from '@renderer/types' +import { uniqBy } from 'lodash' export interface LlmState { providers: Provider[] } const initialState: LlmState = { - providers: [] + providers: [ + { + id: 'openai', + name: 'OpenAI', + apiKey: '', + apiHost: 'https://api.openai.com', + apiPath: '/v1/chat/completions', + isSystem: true, + models: [] + }, + { + id: 'silicon', + name: 'Silicon', + apiKey: '', + apiHost: 'https://api.siliconflow.cn', + apiPath: '/v1/chat/completions', + isSystem: true, + models: [] + }, + { + id: 'deepseek', + name: 'deepseek', + apiKey: '', + apiHost: 'https://api.deepseek.com', + apiPath: '/v1/chat/completions', + isSystem: true, + models: [] + }, + { + id: 'groq', + name: 'Groq', + apiKey: '', + apiHost: 'https://api.groq.com', + apiPath: '/v1/chat/completions', + isSystem: true, + models: [] + } + ] } const settingsSlice = createSlice({ name: 'settings', initialState, reducers: { - updateProvider: () => { - // + updateProvider: (state, action: PayloadAction) => { + state.providers = state.providers.map((p) => (p.id === action.payload.id ? { ...p, ...action.payload } : p)) + }, + addProvider: (state, action: PayloadAction) => { + state.providers.push(action.payload) + }, + removeProvider: (state, action: PayloadAction<{ id: string }>) => { + state.providers = state.providers.filter((p) => p.id !== action.payload.id && !p.isSystem) + }, + addModel: (state, action: PayloadAction<{ providerId: string; model: Model }>) => { + state.providers = state.providers.map((p) => + p.id === action.payload.providerId + ? { + ...p, + models: uniqBy(p.models.concat(action.payload.model), 'id') + } + : p + ) + }, + removeModel: (state, action: PayloadAction<{ providerId: string; model: Model }>) => { + state.providers = state.providers.map((p) => + p.id === action.payload.providerId + ? { + ...p, + models: p.models.filter((m) => m.id !== action.payload.model.id) + } + : p + ) } } }) -export const { updateProvider } = settingsSlice.actions +export const { updateProvider, addProvider, removeProvider, addModel, removeModel } = settingsSlice.actions export default settingsSlice.reducer diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 0ff982e6..38b33706 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -27,3 +27,29 @@ export type User = { avatar: string email: string } + +export type Provider = { + id: string + name: string + apiKey: string + apiHost: string + apiPath: string + models: Model[] + isSystem?: boolean + isDefault?: boolean +} + +export type Model = { + id: string + name: string + group: string + temperature: number +} + +export type SystemAssistant = { + id: string + name: string + description: string + prompt: string + group: string +}