feat: add models

This commit is contained in:
kangfenmao 2024-07-04 15:26:29 +08:00
parent be71f659ac
commit 392dfcee13
26 changed files with 978 additions and 56 deletions

View File

@ -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<Props> = ({ 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 (
<Modal
title={String(provider.name + ' Models').toUpperCase()}
open={open}
onOk={onOk}
onCancel={onCancel}
afterClose={onClose}
footer={null}
width="600px"
styles={{
content: { padding: 0 },
header: { padding: 22, paddingBottom: 15 }
}}>
<ListContainer>
{Object.keys(systemModelGroups).map((group) => (
<div key={group}>
<ListHeader key={group}>{group}</ListHeader>
{systemModelGroups[group].map((model) => {
const hasModel = provider.models.find((m) => m.id === model.id)
return (
<ListItem key={model.id}>
<ListItemName>{model.id}</ListItemName>
{hasModel ? (
<Button type="default" onClick={() => onRemoveModel(model)} icon={<MinusOutlined />} />
) : (
<Button type="primary" onClick={() => onAddModel(model)} icon={<PlusOutlined />} />
)}
</ListItem>
)
})}
</div>
))}
</ListContainer>
</Modal>
)
}
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<any>((resolve) => {
this.topviewId = TopView.show(
<PopupContainer
{...props}
resolve={(v) => {
resolve(v)
this.hide()
}}
/>
)
})
}
}

View File

@ -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<Props> = ({ title, resolve }) => {
const [open, setOpen] = useState(true)
const onOk = () => {
setOpen(false)
}
const onCancel = () => {
setOpen(false)
}
const onClose = () => {
resolve({})
}
return (
<Modal title={title} open={open} onOk={onOk} onCancel={onCancel} afterClose={onClose}>
<Box mb={8}>Name</Box>
</Modal>
)
}
export default class TemplatePopup {
static topviewId = 0
static hide() {
TopView.hide(this.topviewId)
}
static show(props: ShowParams) {
return new Promise<any>((resolve) => {
this.topviewId = TopView.show(
<PopupContainer
{...props}
resolve={(v) => {
resolve(v)
this.hide()
}}
/>
)
})
}
}

View File

@ -28,7 +28,7 @@ const Sidebar: FC = () => {
</Menus> </Menus>
</MainMenus> </MainMenus>
<Menus> <Menus>
<StyledLink to="/settings/general"> <StyledLink to="/settings/common">
<Icon className={pathname.startsWith('/settings') ? 'active' : ''}> <Icon className={pathname.startsWith('/settings') ? 'active' : ''}>
<i className="iconfont icon-setting"></i> <i className="iconfont icon-setting"></i>
</Icon> </Icon>

View File

@ -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'
}
]

View File

@ -0,0 +1,222 @@
import { Model } from '@renderer/types'
export const SYSTEM_MODELS: Record<string, Model[]> = {
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
}
]
}

View File

@ -11,7 +11,7 @@ import {
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import localforage from 'localforage' import localforage from 'localforage'
export default function useAssistants() { export function useAssistants() {
const { assistants } = useAppSelector((state) => state.assistants) const { assistants } = useAppSelector((state) => state.assistants)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()

View File

@ -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))
}

View File

@ -1,13 +1,81 @@
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' 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 { FC } from 'react'
import styled from 'styled-components' 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 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 ( return (
<Container> <Container>
{contextHolder}
<Navbar> <Navbar>
<NavbarCenter>Assistant Market</NavbarCenter> <NavbarCenter>Assistant Market</NavbarCenter>
</Navbar> </Navbar>
<ContentContainer>
{Object.keys(assistantGroups).map((group) => (
<div key={group}>
<Title level={3} key={group} style={{ marginBottom: 16 }}>
{group}
</Title>
<Row gutter={16}>
{assistantGroups[group].map((assistant, index) => {
const added = find(assistants, { id: assistant.id })
return (
<Col span={6} key={group + index} style={{ marginBottom: 16 }}>
<AssistantCard>
<AssistantHeader>
<Title level={5} style={{ marginBottom: 0, color: '#00b96b' }}>
{assistant.name}
</Title>
{added && <Button type="primary" shape="circle" size="small" icon={<CheckOutlined />} />}
{!added && (
<Tooltip placement="top" title=" Add to assistant list " arrow>
<Button
type="default"
shape="circle"
size="small"
icon={<PlusOutlined />}
onClick={() => onAddAssistant(assistant)}
/>
</Tooltip>
)}
</AssistantHeader>
<AssistantCardDescription>{assistant.description}</AssistantCardDescription>
<AssistantCardPrompt>{assistant.prompt}</AssistantCardPrompt>
</AssistantCard>
</Col>
)
})}
</Row>
</div>
))}
</ContentContainer>
</Container> </Container>
) )
} }
@ -15,6 +83,46 @@ const AppsPage: FC = () => {
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
flex: 1; 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 export default AppsPage

View File

@ -1,5 +1,5 @@
import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar' 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 { FC, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Chat from './components/Chat/Chat' import Chat from './components/Chat/Chat'

View File

@ -1,6 +1,6 @@
import { FC, useRef } from 'react' import { FC, useRef } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import useAssistants from '@renderer/hooks/useAssistants' import { useAssistants } from '@renderer/hooks/useAssistant'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { Dropdown, MenuProps } from 'antd' import { Dropdown, MenuProps } from 'antd'
import { last } from 'lodash' import { last } from 'lodash'

View File

@ -5,7 +5,7 @@ import Inputbar from './Inputbar'
import Conversations from './Conversations' import Conversations from './Conversations'
import { Flex } from 'antd' import { Flex } from 'antd'
import TopicList from './TopicList' import TopicList from './TopicList'
import { useAssistant } from '@renderer/hooks/useAssistants' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useActiveTopic } from '@renderer/hooks/useTopic' import { useActiveTopic } from '@renderer/hooks/useTopic'
interface Props { interface Props {

View File

@ -7,7 +7,7 @@ import MessageItem from './Message'
import { reverse } from 'lodash' import { reverse } from 'lodash'
import hljs from 'highlight.js' import hljs from 'highlight.js'
import { fetchChatCompletion, fetchConversationSummary } from '@renderer/services/api' 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 { 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'

View File

@ -6,7 +6,7 @@ import styled from 'styled-components'
import { MoreOutlined } from '@ant-design/icons' import { MoreOutlined } from '@ant-design/icons'
import { Button, Popconfirm, Tooltip } from 'antd' import { Button, Popconfirm, Tooltip } from 'antd'
import { useShowRightSidebar } from '@renderer/hooks/useStore' 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' import { ClearOutlined, HistoryOutlined, PlusCircleOutlined } from '@ant-design/icons'
interface Props { interface Props {

View File

@ -1,5 +1,5 @@
import PromptPopup from '@renderer/components/Popups/PromptPopup' 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 { useShowRightSidebar } from '@renderer/hooks/useStore'
import { fetchConversationSummary } from '@renderer/services/api' import { fetchConversationSummary } from '@renderer/services/api'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'

View File

@ -5,6 +5,8 @@ const AboutSettings: FC = () => {
return <Container>About</Container> return <Container>About</Container>
} }
const Container = styled.div`` const Container = styled.div`
padding: 20px;
`
export default AboutSettings export default AboutSettings

View File

@ -5,6 +5,7 @@ const CommonSettings: FC = () => {
return <Container>Common Settings</Container> return <Container>Common Settings</Container>
} }
const Container = styled.div`` const Container = styled.div`
padding: 20px;
`
export default CommonSettings export default CommonSettings

View File

@ -5,6 +5,7 @@ const DefaultAssistantSetting: FC = () => {
return <Container>Default Assistant</Container> return <Container>Default Assistant</Container>
} }
const Container = styled.div`` const Container = styled.div`
padding: 20px;
`
export default DefaultAssistantSetting export default DefaultAssistantSetting

View File

@ -5,6 +5,7 @@ const DeveloperSetting: FC = () => {
return <Container>Developer</Container> return <Container>Developer</Container>
} }
const Container = styled.div`` const Container = styled.div`
padding: 20px;
`
export default DeveloperSetting export default DeveloperSetting

View File

@ -1,36 +1,64 @@
import { Collapse } from 'antd' import { useSystemProviders } from '@renderer/hooks/useProvider'
import { FC } from 'react' import { Provider } from '@renderer/types'
import { FC, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import ModalProviderSetting from './components/ModalProviderSetting'
// OpenAI Silicon deepseek Groq
const LanguageModelsSettings: FC = () => { const LanguageModelsSettings: FC = () => {
const providers = useSystemProviders()
const [selectedProvider, setSelectedProvider] = useState<Provider>(providers[0])
return ( return (
<Container> <Container>
<Collapse style={{ width: '100%', marginBottom: 10 }}> <ProviderListContainer>
<Collapse.Panel header="OpenAI" key="openai"> {providers.map((provider) => (
<p>OpenAI</p> <ProviderListItem
</Collapse.Panel> key={JSON.stringify(provider)}
</Collapse> className={provider.id === selectedProvider?.id ? 'active' : ''}
<Collapse style={{ width: '100%', marginBottom: 10 }}> onClick={() => setSelectedProvider(provider)}>
<Collapse.Panel header="Silicon" key="silicon"> {provider.name}
<p>Silicon</p> </ProviderListItem>
</Collapse.Panel> ))}
</Collapse> </ProviderListContainer>
<Collapse style={{ width: '100%', marginBottom: 10 }}> <ModalProviderSetting provider={selectedProvider} />
<Collapse.Panel header="deepseek" key="deepseek">
<p>deepseek</p>
</Collapse.Panel>
</Collapse>
<Collapse style={{ width: '100%', marginBottom: 10 }}>
<Collapse.Panel header="Groq" key="groq">
<p>Groq</p>
</Collapse.Panel>
</Collapse>
</Container> </Container>
) )
} }
const Container = styled.div` const Container = styled.div`
width: 100%; 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 export default LanguageModelsSettings

View File

@ -96,8 +96,7 @@ const SettingContent = styled.div`
display: flex; display: flex;
height: 100%; height: 100%;
flex: 1; flex: 1;
border-right: 1px solid var(--color-border); border-right: 0.5px solid var(--color-border);
padding: 20px;
` `
export default SettingsPage export default SettingsPage

View File

@ -5,6 +5,7 @@ const SystemAssistantSettings: FC = () => {
return <Container>System Assistant</Container> return <Container>System Assistant</Container>
} }
const Container = styled.div`` const Container = styled.div`
padding: 20px;
`
export default SystemAssistantSettings export default SystemAssistantSettings

View File

@ -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<Props> = ({ 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 (
<Container>
<Title>{provider.name}</Title>
<Divider style={{ width: '100%', margin: '10px 0' }} />
<SubTitle>API Key</SubTitle>
<Input value={apiKey} placeholder="API Key" onChange={(e) => setApiKey(e.target.value)} onBlur={onUpdateApiKey} />
<SubTitle>API Host</SubTitle>
<Input
value={apiHost}
placeholder="API Host"
onChange={(e) => setApiHost(e.target.value)}
onBlur={onUpdateApiHost}
/>
<SubTitle>API Path</SubTitle>
<Input
value={apiPath}
placeholder="API Path"
onChange={(e) => setApiPath(e.target.value)}
onBlur={onUpdateApiPath}
/>
<SubTitle>Models</SubTitle>
{Object.keys(modelGroups).map((group) => (
<Card key={group} type="inner" title={group} style={{ marginBottom: '10px' }} size="small">
{modelGroups[group].map((model) => (
<ModelListItem key={model.id}>{model.id}</ModelListItem>
))}
</Card>
))}
<Button type="primary" style={{ width: '100px', marginTop: '10px' }} onClick={onAddModal}>
Edit Models
</Button>
</Container>
)
}
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

View File

@ -6,5 +6,6 @@ export const EVENT_NAMES = {
SEND_MESSAGE: 'SEND_MESSAGE', SEND_MESSAGE: 'SEND_MESSAGE',
AI_CHAT_COMPLETION: 'AI_CHAT_COMPLETION', AI_CHAT_COMPLETION: 'AI_CHAT_COMPLETION',
AI_AUTO_RENAME: 'AI_AUTO_RENAME', AI_AUTO_RENAME: 'AI_AUTO_RENAME',
CLEAR_CONVERSATION: 'CLEAR_CONVERSATION' CLEAR_CONVERSATION: 'CLEAR_CONVERSATION',
ADD_ASSISTANT: 'ADD_ASSISTANT'
} }

View File

@ -4,10 +4,12 @@ import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, R
import storage from 'redux-persist/lib/storage' import storage from 'redux-persist/lib/storage'
import assistants from './assistants' import assistants from './assistants'
import settings from './settings' import settings from './settings'
import llm from './llm'
const rootReducer = combineReducers({ const rootReducer = combineReducers({
assistants, assistants,
settings settings,
llm
}) })
const store = configureStore({ const store = configureStore({

View File

@ -1,31 +1,88 @@
import { createSlice } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { Model, Provider } from '@renderer/types'
type Provider = { import { uniqBy } from 'lodash'
id: string
name: string
apiKey: string
apiUrl: string
url: string
}
export interface LlmState { export interface LlmState {
providers: Provider[] providers: Provider[]
} }
const initialState: LlmState = { 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({ const settingsSlice = createSlice({
name: 'settings', name: 'settings',
initialState, initialState,
reducers: { reducers: {
updateProvider: () => { updateProvider: (state, action: PayloadAction<Provider>) => {
// state.providers = state.providers.map((p) => (p.id === action.payload.id ? { ...p, ...action.payload } : p))
},
addProvider: (state, action: PayloadAction<Provider>) => {
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 export default settingsSlice.reducer

View File

@ -27,3 +27,29 @@ export type User = {
avatar: string avatar: string
email: 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
}