Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be0799a4c6 | ||
|
|
d0f5547419 | ||
|
|
076011b02b | ||
|
|
ba5c70c45a | ||
|
|
ebe74ffd05 | ||
|
|
d0bea0491f | ||
|
|
514e1a4796 | ||
|
|
2ffedadee4 | ||
|
|
7b72783ae7 | ||
|
|
4485a00395 | ||
|
|
77c0952635 | ||
|
|
e1c7a25b87 | ||
|
|
b0c479190c |
@@ -56,4 +56,6 @@ electronDownload:
|
||||
afterSign: scripts/notarize.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
错误修复,优化体验
|
||||
增加 Gemini、豆包、阶跃星辰等服务商支持
|
||||
修复 Anthropic 回复问题
|
||||
新的 Windows, Linux 侧边栏样式
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cherry-studio",
|
||||
"version": "0.5.4",
|
||||
"version": "0.5.6",
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "kangfenmao@qq.com",
|
||||
@@ -37,6 +37,7 @@
|
||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@google/generative-ai": "^0.16.0",
|
||||
"@hello-pangea/dnd": "^16.6.0",
|
||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||
"@reduxjs/toolkit": "^2.2.5",
|
||||
@@ -45,8 +46,8 @@
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"ahooks": "^3.8.0",
|
||||
"antd": "^5.18.3",
|
||||
"axios": "^1.7.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"dayjs": "^1.11.11",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; connect-src *; script-src 'self' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data:" />
|
||||
content="default-src 'self'; connect-src *; script-src 'self' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data:; frame-src *" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
BIN
src/renderer/src/assets/images/models/doubao.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/renderer/src/assets/images/models/embedding.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/renderer/src/assets/images/models/gemini.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
67
src/renderer/src/assets/images/models/palm.svg
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Standard_product_icon__x28_1:1_x29_"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="192px" height="192px"
|
||||
viewBox="0 0 192 192" enable-background="new 0 0 192 192" xml:space="preserve">
|
||||
<symbol id="material_x5F_product_x5F_standard_x5F_icon_x5F_keylines_00000077318920148093339210000006245950728745084294_" viewBox="-96 -96 192 192">
|
||||
<g opacity="0.4">
|
||||
<defs>
|
||||
<path id="SVGID_1_" opacity="0.4" d="M-96,96V-96H96V96H-96z"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_00000071517564283228984050000017848131202901217410_">
|
||||
<use xlink:href="#SVGID_1_" overflow="visible"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)">
|
||||
<g>
|
||||
<path d="M95.75,95.75v-191.5h-191.5v191.5H95.75 M96,96H-96V-96H96V96L96,96z"/>
|
||||
</g>
|
||||
<circle fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" cx="0" cy="0" r="64"/>
|
||||
</g>
|
||||
|
||||
<circle clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)" fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" cx="0" cy="0" r="88"/>
|
||||
|
||||
<path clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)" fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" d="
|
||||
M64,76H-64c-6.6,0-12-5.4-12-12V-64c0-6.6,5.4-12,12-12H64c6.6,0,12,5.4,12,12V64C76,70.6,70.6,76,64,76z"/>
|
||||
|
||||
<path clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)" fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" d="
|
||||
M52,88H-52c-6.6,0-12-5.4-12-12V-76c0-6.6,5.4-12,12-12H52c6.6,0,12,5.4,12,12V76C64,82.6,58.6,88,52,88z"/>
|
||||
|
||||
<path clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)" fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" d="
|
||||
M76,64H-76c-6.6,0-12-5.4-12-12V-52c0-6.6,5.4-12,12-12H76c6.6,0,12,5.4,12,12V52C88,58.6,82.6,64,76,64z"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<rect id="bounding_box_1_" display="none" fill="none" width="192" height="192"/>
|
||||
<g id="art_layer">
|
||||
<g>
|
||||
<path fill="#F9AB00" d="M96,181.92L96,181.92c6.63,0,12-5.37,12-12v-104H84v104C84,176.55,89.37,181.92,96,181.92z"/>
|
||||
<g>
|
||||
<path fill="#5BB974" d="M143.81,103.87C130.87,90.94,111.54,88.32,96,96l51.37,51.37c2.12,2.12,5.77,1.28,6.67-1.57
|
||||
C158.56,131.49,155.15,115.22,143.81,103.87z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#129EAF" d="M48.19,103.87C61.13,90.94,80.46,88.32,96,96l-51.37,51.37c-2.12,2.12-5.77,1.28-6.67-1.57
|
||||
C33.44,131.49,36.85,115.22,48.19,103.87z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#AF5CF7" d="M140,64c-20.44,0-37.79,13.4-44,32h81.24c3.33,0,5.55-3.52,4.04-6.49C173.56,74.36,157.98,64,140,64z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FF8BCB" d="M104.49,42.26C90.03,56.72,87.24,78.45,96,96l57.45-57.45c2.36-2.36,1.44-6.42-1.73-7.45
|
||||
C135.54,25.85,117.2,29.55,104.49,42.26z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FA7B17" d="M87.51,42.26C101.97,56.72,104.76,78.45,96,96L38.55,38.55c-2.36-2.36-1.44-6.42,1.73-7.45
|
||||
C56.46,25.85,74.8,29.55,87.51,42.26z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#4285F4" d="M52,64c20.44,0,37.79,13.4,44,32H14.76c-3.33,0-5.55-3.52-4.04-6.49C18.44,74.36,34.02,64,52,64z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="keylines" display="none">
|
||||
|
||||
<use xlink:href="#material_x5F_product_x5F_standard_x5F_icon_x5F_keylines_00000077318920148093339210000006245950728745084294_" width="192" height="192" id="material_x5F_product_x5F_standard_x5F_icon_x5F_keylines" x="-96" y="-96" transform="matrix(1 0 0 -1 96 96)" display="inline" overflow="visible"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/renderer/src/assets/images/models/step.jpg
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
src/renderer/src/assets/images/providers/doubao.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/renderer/src/assets/images/providers/gemini.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/renderer/src/assets/images/providers/graph-rag.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/renderer/src/assets/images/providers/graph-rag.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/renderer/src/assets/images/providers/openai.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/renderer/src/assets/images/providers/stepfun.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
@@ -162,3 +162,40 @@ body,
|
||||
.ant-segmented-group {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.dragable {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.dragdisable {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.minapp-drawer {
|
||||
.ant-drawer-header-title {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.ant-drawer-close {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 15px;
|
||||
padding: 15px;
|
||||
margin-right: -5px;
|
||||
-webkit-app-region: no-drag;
|
||||
z-index: 100000;
|
||||
}
|
||||
.ant-drawer-header {
|
||||
height: calc(var(--navbar-height) + 0.5px);
|
||||
background: var(--navbar-background);
|
||||
width: calc(100vw - var(--sidebar-width));
|
||||
padding-right: 10px !important;
|
||||
border-bottom: 0.5px solid var(--color-border);
|
||||
margin-top: -0.5px;
|
||||
}
|
||||
.ant-drawer-body {
|
||||
padding: 0;
|
||||
}
|
||||
.minapp-mask {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
125
src/renderer/src/components/MinApp/index.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import { CloseOutlined, ExportOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import store from '@renderer/store'
|
||||
import { setMinappShow } from '@renderer/store/runtime'
|
||||
import { Drawer } from 'antd'
|
||||
import { useRef, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { TopView } from '../TopView'
|
||||
|
||||
interface ShowParams {
|
||||
title: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface Props extends ShowParams {
|
||||
resolve: (data: any) => void
|
||||
}
|
||||
|
||||
const PopupContainer: React.FC<Props> = ({ title, url, resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null)
|
||||
|
||||
const onClose = () => {
|
||||
setOpen(false)
|
||||
setTimeout(() => resolve({}), 300)
|
||||
}
|
||||
|
||||
const onReload = () => {
|
||||
if (iframeRef.current) {
|
||||
iframeRef.current.src = url
|
||||
}
|
||||
}
|
||||
|
||||
const onOpenLink = () => {
|
||||
window.api.openWebsite(url)
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={title}
|
||||
placement="bottom"
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
mask={true}
|
||||
rootClassName="minapp-drawer"
|
||||
maskClassName="minapp-mask"
|
||||
height={'100%'}
|
||||
maskClosable={false}
|
||||
closeIcon={null}
|
||||
style={{ marginLeft: 'var(--sidebar-width)' }}>
|
||||
<Frame src={url} ref={iframeRef} />
|
||||
<ButtonsGroup>
|
||||
<Button onClick={onReload}>
|
||||
<ReloadOutlined />
|
||||
</Button>
|
||||
<Button onClick={onOpenLink}>
|
||||
<ExportOutlined />
|
||||
</Button>
|
||||
<Button onClick={onClose}>
|
||||
<CloseOutlined />
|
||||
</Button>
|
||||
</ButtonsGroup>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
const Frame = styled.iframe`
|
||||
width: calc(100vw - var(--sidebar-width));
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
border: none;
|
||||
`
|
||||
|
||||
const ButtonsGroup = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: var(--navbar-height);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 0 10px;
|
||||
`
|
||||
|
||||
const Button = styled.div`
|
||||
-webkit-app-region: no-drag;
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-text-2);
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
&:hover {
|
||||
color: var(--color-text-1);
|
||||
background-color: var(--color-background-mute);
|
||||
}
|
||||
`
|
||||
|
||||
export default class MinApp {
|
||||
static topviewId = 0
|
||||
static close() {
|
||||
TopView.hide('MinApp')
|
||||
store.dispatch(setMinappShow(false))
|
||||
}
|
||||
static start(props: ShowParams) {
|
||||
store.dispatch(setMinappShow(true))
|
||||
return new Promise<any>((resolve) => {
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.close()
|
||||
}}
|
||||
/>,
|
||||
'MinApp'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { useRuntime } from '@renderer/hooks/useStore'
|
||||
import { FC, PropsWithChildren } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
type Props = PropsWithChildren & JSX.IntrinsicElements['div']
|
||||
|
||||
export const Navbar: FC<Props> = ({ children, ...props }) => {
|
||||
return <NavbarContainer {...props}>{children}</NavbarContainer>
|
||||
const { minappShow } = useRuntime()
|
||||
const backgroundColor = minappShow ? 'var(--color-background)' : 'var(--navbar-background)'
|
||||
|
||||
return (
|
||||
<NavbarContainer {...props} style={{ backgroundColor }}>
|
||||
{children}
|
||||
</NavbarContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export const NavbarLeft: FC<Props> = ({ children, ...props }) => {
|
||||
@@ -26,15 +34,16 @@ const NavbarContainer = styled.div`
|
||||
flex-direction: row;
|
||||
min-height: var(--navbar-height);
|
||||
max-height: var(--navbar-height);
|
||||
-webkit-app-region: drag;
|
||||
margin-left: calc(var(--sidebar-width) * -1);
|
||||
margin-left: ${isMac ? 'calc(var(--sidebar-width) * -1)' : 0};
|
||||
padding-left: ${isMac ? 'var(--sidebar-width)' : 0};
|
||||
border-bottom: 0.5px solid var(--color-border);
|
||||
background-color: var(--navbar-background);
|
||||
transition: background-color 0.3s ease;
|
||||
-webkit-app-region: drag;
|
||||
`
|
||||
|
||||
const NavbarLeftContainer = styled.div`
|
||||
min-width: ${isMac ? 'var(--assistants-width)' : 'calc(var(--sidebar-width) + var(--assistants-width))'};
|
||||
min-width: var(--assistants-width);
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -56,5 +65,5 @@ const NavbarRightContainer = styled.div`
|
||||
min-width: var(--settings-width);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
padding: 0 12px;
|
||||
`
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { TranslationOutlined } from '@ant-design/icons'
|
||||
import Logo from '@renderer/assets/images/logo.png'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
import { useRuntime } from '@renderer/hooks/useStore'
|
||||
import { Avatar } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
@@ -8,11 +11,12 @@ import styled from 'styled-components'
|
||||
const Sidebar: FC = () => {
|
||||
const { pathname } = useLocation()
|
||||
const avatar = useAvatar()
|
||||
const { minappShow } = useRuntime()
|
||||
|
||||
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Container style={{ backgroundColor: minappShow ? 'var(--color-background)' : 'var(--sidebar-background)' }}>
|
||||
<StyledLink to="/">
|
||||
<AvatarImg src={avatar || Logo} draggable={false} />
|
||||
</StyledLink>
|
||||
@@ -52,21 +56,21 @@ const Container = styled.div`
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
width: var(--sidebar-width);
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
height: ${isMac ? 'calc(100vh - var(--navbar-height))' : '100vh'};
|
||||
-webkit-app-region: drag !important;
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
margin-top: var(--navbar-height);
|
||||
margin-bottom: var(--navbar-height);
|
||||
margin-top: ${isMac ? 'var(--navbar-height)' : 0};
|
||||
background-color: var(--sidebar-background);
|
||||
transition: background-color 0.3s ease;
|
||||
`
|
||||
|
||||
const AvatarImg = styled.img`
|
||||
border-radius: 50%;
|
||||
const AvatarImg = styled(Avatar)`
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-color: var(--color-background-soft);
|
||||
margin: 5px 0;
|
||||
margin-top: 5px;
|
||||
margin-bottom: ${isMac ? '12px' : '12px'};
|
||||
margin-top: ${isMac ? '5px' : '2px'};
|
||||
border: none;
|
||||
`
|
||||
const MainMenus = styled.div`
|
||||
display: flex;
|
||||
|
||||
@@ -33,6 +33,22 @@ export const SYSTEM_MODELS: Record<string, SystemModel[]> = {
|
||||
enabled: true
|
||||
}
|
||||
],
|
||||
gemini: [
|
||||
{
|
||||
id: 'gemini-1.5-flash',
|
||||
provider: 'gemini',
|
||||
name: 'Gemini 1.5 Flash',
|
||||
group: 'Gemini 1.5',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 'gemini-1.5-pro-exp-0801',
|
||||
provider: 'gemini',
|
||||
name: 'Gemini 1.5 Pro Experimental 0801',
|
||||
group: 'Gemini 1.5',
|
||||
enabled: true
|
||||
}
|
||||
],
|
||||
silicon: [
|
||||
{
|
||||
id: 'Qwen/Qwen2-7B-Instruct',
|
||||
@@ -320,6 +336,23 @@ export const SYSTEM_MODELS: Record<string, SystemModel[]> = {
|
||||
enabled: true
|
||||
}
|
||||
],
|
||||
stepfun: [
|
||||
{
|
||||
id: 'step-1-8k',
|
||||
provider: 'stepfun',
|
||||
name: 'Step 1 8K',
|
||||
group: 'Step 1',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 'step-1-flash',
|
||||
provider: 'stepfun',
|
||||
name: 'Step 1 Flash',
|
||||
group: 'Step 1',
|
||||
enabled: true
|
||||
}
|
||||
],
|
||||
doubao: [],
|
||||
aihubmix: [
|
||||
{
|
||||
id: 'gpt-4o-mini',
|
||||
|
||||
@@ -3,24 +3,33 @@ import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.jpeg'
|
||||
import ChatGPTModelLogo from '@renderer/assets/images/models/chatgpt.jpeg'
|
||||
import ClaudeModelLogo from '@renderer/assets/images/models/claude.png'
|
||||
import DeepSeekModelLogo from '@renderer/assets/images/models/deepseek.png'
|
||||
import DoubaoModelLogo from '@renderer/assets/images/models/doubao.png'
|
||||
import EmbeddingModelLogo from '@renderer/assets/images/models/embedding.png'
|
||||
import GeminiModelLogo from '@renderer/assets/images/models/gemini.png'
|
||||
import GemmaModelLogo from '@renderer/assets/images/models/gemma.jpeg'
|
||||
import LlamaModelLogo from '@renderer/assets/images/models/llama.jpeg'
|
||||
import MicrosoftModelLogo from '@renderer/assets/images/models/microsoft.png'
|
||||
import MixtralModelLogo from '@renderer/assets/images/models/mixtral.jpeg'
|
||||
import PalmModelLogo from '@renderer/assets/images/models/palm.svg'
|
||||
import QwenModelLogo from '@renderer/assets/images/models/qwen.png'
|
||||
import StepModelLogo from '@renderer/assets/images/models/step.jpg'
|
||||
import YiModelLogo from '@renderer/assets/images/models/yi.svg'
|
||||
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.jpg'
|
||||
import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.jpeg'
|
||||
import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png'
|
||||
import DashScopeProviderLogo from '@renderer/assets/images/providers/dashscope.png'
|
||||
import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png'
|
||||
import DoubaoProviderLogo from '@renderer/assets/images/providers/doubao.png'
|
||||
import GeminiProviderLogo from '@renderer/assets/images/providers/gemini.png'
|
||||
import GraphRagProviderLogo from '@renderer/assets/images/providers/graph-rag.png'
|
||||
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
|
||||
import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.jpeg'
|
||||
import MoonshotModelLogo from '@renderer/assets/images/providers/moonshot.jpeg'
|
||||
import OllamaProviderLogo from '@renderer/assets/images/providers/ollama.png'
|
||||
import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.jpeg'
|
||||
import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png'
|
||||
import OpenRouterProviderLogo from '@renderer/assets/images/providers/openrouter.png'
|
||||
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
|
||||
import StepFunProviderLogo from '@renderer/assets/images/providers/stepfun.png'
|
||||
import YiProviderLogo from '@renderer/assets/images/providers/yi.svg'
|
||||
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
|
||||
|
||||
@@ -52,6 +61,14 @@ export function getProviderLogo(providerId: string) {
|
||||
return AnthropicProviderLogo
|
||||
case 'aihubmix':
|
||||
return AiHubMixProviderLogo
|
||||
case 'gemini':
|
||||
return GeminiProviderLogo
|
||||
case 'stepfun':
|
||||
return StepFunProviderLogo
|
||||
case 'doubao':
|
||||
return DoubaoProviderLogo
|
||||
case 'graphrag-kylin-mountain':
|
||||
return GraphRagProviderLogo
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
@@ -75,7 +92,13 @@ export function getModelLogo(modelId: string) {
|
||||
moonshot: MoonshotModelLogo,
|
||||
phi: MicrosoftModelLogo,
|
||||
baichuan: BaichuanModelLogo,
|
||||
claude: ClaudeModelLogo
|
||||
claude: ClaudeModelLogo,
|
||||
gemini: GeminiModelLogo,
|
||||
embedding: EmbeddingModelLogo,
|
||||
bison: PalmModelLogo,
|
||||
palm: PalmModelLogo,
|
||||
step: StepModelLogo,
|
||||
'ep-202': DoubaoModelLogo
|
||||
}
|
||||
|
||||
for (const key in logoMap) {
|
||||
@@ -100,6 +123,18 @@ export const PROVIDER_CONFIG = {
|
||||
models: 'https://platform.openai.com/docs/models'
|
||||
}
|
||||
},
|
||||
gemini: {
|
||||
api: {
|
||||
url: 'https://generativelanguage.googleapis.com',
|
||||
editable: false
|
||||
},
|
||||
websites: {
|
||||
official: 'https://gemini.google.com/',
|
||||
apiKey: 'https://aistudio.google.com/app/apikey',
|
||||
docs: 'https://ai.google.dev/gemini-api/docs',
|
||||
models: 'https://ai.google.dev/gemini-api/docs/models/gemini'
|
||||
}
|
||||
},
|
||||
silicon: {
|
||||
api: {
|
||||
url: 'https://cloud.siliconflow.cn',
|
||||
@@ -184,6 +219,36 @@ export const PROVIDER_CONFIG = {
|
||||
models: 'https://dashscope.console.aliyun.com/model'
|
||||
}
|
||||
},
|
||||
stepfun: {
|
||||
api: {
|
||||
url: 'https://api.stepfun.com',
|
||||
editable: false
|
||||
},
|
||||
websites: {
|
||||
official: 'https://platform.stepfun.com/',
|
||||
apiKey: 'https://platform.stepfun.com/interface-key',
|
||||
docs: 'https://platform.stepfun.com/docs/overview/concept',
|
||||
models: 'https://platform.stepfun.com/docs/llm/text'
|
||||
}
|
||||
},
|
||||
doubao: {
|
||||
api: {
|
||||
url: 'https://ark.cn-beijing.volces.com/api/v3/',
|
||||
editable: true
|
||||
},
|
||||
websites: {
|
||||
official: 'https://console.volcengine.com/ark/',
|
||||
apiKey: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey',
|
||||
docs: 'https://www.volcengine.com/docs/82379/1182403',
|
||||
models: 'https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint'
|
||||
}
|
||||
},
|
||||
'graphrag-kylin-mountain': {
|
||||
api: {
|
||||
url: '',
|
||||
editable: true
|
||||
}
|
||||
},
|
||||
openrouter: {
|
||||
api: {
|
||||
url: 'https://openrouter.ai/api/v1/',
|
||||
|
||||
@@ -16,3 +16,7 @@ export function useActiveTopic(assistant: Assistant) {
|
||||
|
||||
return { activeTopic, setActiveTopic }
|
||||
}
|
||||
|
||||
export function getTopic(assistant: Assistant, topicId: string) {
|
||||
return assistant?.topics.find((topic) => topic.id === topicId)
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ const resources = {
|
||||
},
|
||||
provider: {
|
||||
openai: 'OpenAI',
|
||||
gemini: 'Gemini',
|
||||
deepseek: 'DeepSeek',
|
||||
moonshot: 'Moonshot',
|
||||
silicon: 'SiliconFlow',
|
||||
@@ -117,7 +118,10 @@ const resources = {
|
||||
baichuan: 'Baichuan',
|
||||
dashscope: 'DashScope',
|
||||
anthropic: 'Anthropic',
|
||||
aihubmix: 'AiHubMix'
|
||||
aihubmix: 'AiHubMix',
|
||||
stepfun: 'StepFun',
|
||||
doubao: 'Doubao',
|
||||
'graphrag-kylin-mountain': 'GraphRAG'
|
||||
},
|
||||
settings: {
|
||||
title: 'Settings',
|
||||
@@ -323,6 +327,7 @@ const resources = {
|
||||
},
|
||||
provider: {
|
||||
openai: 'OpenAI',
|
||||
gemini: 'Gemini',
|
||||
deepseek: '深度求索',
|
||||
moonshot: '月之暗面',
|
||||
silicon: '硅基流动',
|
||||
@@ -334,7 +339,10 @@ const resources = {
|
||||
baichuan: '百川',
|
||||
dashscope: '阿里云灵积',
|
||||
anthropic: 'Anthropic',
|
||||
aihubmix: 'AiHubMix'
|
||||
aihubmix: 'AiHubMix',
|
||||
stepfun: '阶跃星辰',
|
||||
doubao: '豆包',
|
||||
'graphrag-kylin-mountain': 'GraphRAG'
|
||||
},
|
||||
settings: {
|
||||
title: '设置',
|
||||
|
||||
@@ -51,7 +51,7 @@ const HomePage: FC = () => {
|
||||
</NavbarLeft>
|
||||
)}
|
||||
<Navigation activeAssistant={activeAssistant} />
|
||||
<NavbarRight style={{ justifyContent: 'flex-end', paddingRight: isWindows ? 140 : 8 }}>
|
||||
<NavbarRight style={{ justifyContent: 'flex-end', paddingRight: isWindows ? 140 : 12 }}>
|
||||
<ThemeSwitch
|
||||
checkedChildren={<i className="iconfont icon-theme icon-dark1" />}
|
||||
unCheckedChildren={<i className="iconfont icon-theme icon-theme-light" />}
|
||||
@@ -120,7 +120,7 @@ export const NewButton = styled.div`
|
||||
|
||||
const ThemeSwitch = styled(Switch)`
|
||||
-webkit-app-region: none;
|
||||
margin-right: 8px;
|
||||
margin-right: 10px;
|
||||
.icon-theme {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
|
||||
const getUserName = useCallback(() => {
|
||||
if (message.id === 'assistant') return assistant?.name
|
||||
if (message.role === 'assistant') return upperFirst(message.modelId)
|
||||
if (message.role === 'assistant') return upperFirst(model.name || model.id)
|
||||
return userName || t('common.you')
|
||||
}, [assistant?.name, message.id, message.modelId, message.role, t, userName])
|
||||
}, [assistant?.name, message.id, message.role, model.id, model.name, t, userName])
|
||||
|
||||
const fontFamily = useMemo(() => {
|
||||
return messageFont === 'serif' ? FONT_FAMILY.replace('sans-serif', 'serif').replace('Ubuntu, ', '') : FONT_FAMILY
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
||||
import { getTopic } from '@renderer/hooks/useTopic'
|
||||
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { estimateHistoryTokenCount, filterMessages } from '@renderer/services/messages'
|
||||
@@ -50,9 +51,10 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
||||
)
|
||||
|
||||
const autoRenameTopic = useCallback(async () => {
|
||||
if (topic.name === t('chat.default.topic.name') && messages.length >= 2) {
|
||||
const _topic = getTopic(assistant, topic.id)
|
||||
if (_topic && _topic.name === t('chat.default.topic.name') && messages.length >= 2) {
|
||||
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
||||
summaryText && updateTopic({ ...topic, name: summaryText })
|
||||
summaryText && updateTopic({ ..._topic, name: summaryText })
|
||||
}
|
||||
}, [assistant, messages, topic, updateTopic])
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
||||
<HelpTextRow>
|
||||
<HelpText>{t('settings.provider.docs_check')} </HelpText>
|
||||
<HelpLink target="_blank" href={docsWebsite}>
|
||||
{t(`provider.${provider.id}`)}
|
||||
{t(`provider.${provider.id}`) + ' '}
|
||||
{t('common.docs')}
|
||||
</HelpLink>
|
||||
<HelpText>{t('common.and')}</HelpText>
|
||||
|
||||
@@ -105,10 +105,13 @@ const ProvidersList: FC = () => {
|
||||
key={JSON.stringify(provider)}
|
||||
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
||||
onClick={() => setSelectedProvider(provider)}>
|
||||
{provider.isSystem && <Avatar src={getProviderLogo(provider.id)} size={25} />}
|
||||
{provider.isSystem && (
|
||||
<Avatar shape="square" src={getProviderLogo(provider.id)} size={25} />
|
||||
)}
|
||||
{!provider.isSystem && (
|
||||
<Avatar
|
||||
size={25}
|
||||
shape="square"
|
||||
style={{ backgroundColor: generateColorFromChar(provider.name), minWidth: 25 }}>
|
||||
{getFirstCharacter(provider.name)}
|
||||
</Avatar>
|
||||
@@ -156,7 +159,6 @@ const ProviderListContainer = styled.div`
|
||||
width: var(--assistants-width);
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
padding: 8px;
|
||||
overflow-y: auto;
|
||||
`
|
||||
|
||||
@@ -164,6 +166,9 @@ const ProviderList = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
`
|
||||
|
||||
const ProviderListItem = styled.div`
|
||||
@@ -200,7 +205,7 @@ const AddButtonWrapper = styled.div`
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
padding: 10px 8px;
|
||||
`
|
||||
|
||||
export default ProvidersList
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources'
|
||||
import { GoogleGenerativeAI } from '@google/generative-ai'
|
||||
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||
import { getOllamaKeepAliveTime } from '@renderer/hooks/useOllama'
|
||||
import { Assistant, Message, Provider, Suggestion } from '@renderer/types'
|
||||
import { removeQuotes } from '@renderer/utils'
|
||||
import { sum, takeRight } from 'lodash'
|
||||
import axios from 'axios'
|
||||
import { isEmpty, sum, takeRight } from 'lodash'
|
||||
import OpenAI from 'openai'
|
||||
import { ChatCompletionCreateParamsNonStreaming, ChatCompletionMessageParam } from 'openai/resources'
|
||||
|
||||
@@ -15,6 +17,7 @@ export default class ProviderSDK {
|
||||
provider: Provider
|
||||
openaiSdk: OpenAI
|
||||
anthropicSdk: Anthropic
|
||||
geminiSdk: GoogleGenerativeAI
|
||||
|
||||
constructor(provider: Provider) {
|
||||
this.provider = provider
|
||||
@@ -22,12 +25,17 @@ export default class ProviderSDK {
|
||||
const baseURL = host.endsWith('/') ? host : `${provider.apiHost}/v1/`
|
||||
this.anthropicSdk = new Anthropic({ apiKey: provider.apiKey, baseURL })
|
||||
this.openaiSdk = new OpenAI({ dangerouslyAllowBrowser: true, apiKey: provider.apiKey, baseURL })
|
||||
this.geminiSdk = new GoogleGenerativeAI(provider.apiKey)
|
||||
}
|
||||
|
||||
private get isAnthropic() {
|
||||
return this.provider.id === 'anthropic'
|
||||
}
|
||||
|
||||
private get isGemini() {
|
||||
return this.provider.id === 'gemini'
|
||||
}
|
||||
|
||||
private get keepAliveTime() {
|
||||
return this.provider.id === 'ollama' ? getOllamaKeepAliveTime() : undefined
|
||||
}
|
||||
@@ -42,49 +50,99 @@ export default class ProviderSDK {
|
||||
const { contextCount, maxTokens } = getAssistantSettings(assistant)
|
||||
|
||||
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
||||
|
||||
const userMessages = takeRight(messages, contextCount + 1).map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
|
||||
if (this.isAnthropic) {
|
||||
await this.anthropicSdk.messages
|
||||
.stream({
|
||||
model: model.id,
|
||||
messages: [systemMessage, ...userMessages].filter(Boolean) as MessageParam[],
|
||||
max_tokens: maxTokens || DEFAULT_MAX_TOKENS,
|
||||
temperature: assistant?.settings?.temperature
|
||||
})
|
||||
.on('text', (text) => onChunk({ text: text || '' }))
|
||||
.on('finalMessage', (message) =>
|
||||
onChunk({
|
||||
usage: {
|
||||
prompt_tokens: message.usage.input_tokens,
|
||||
completion_tokens: message.usage.output_tokens,
|
||||
total_tokens: sum(Object.values(message.usage))
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stream = this.anthropicSdk.messages
|
||||
.stream({
|
||||
model: model.id,
|
||||
messages: userMessages.filter(Boolean) as MessageParam[],
|
||||
max_tokens: maxTokens || DEFAULT_MAX_TOKENS,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
system: assistant.prompt,
|
||||
stream: true
|
||||
})
|
||||
)
|
||||
} else {
|
||||
// @ts-ignore key is not typed
|
||||
const stream = await this.openaiSdk.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
|
||||
stream: true,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
max_tokens: maxTokens,
|
||||
keep_alive: this.keepAliveTime
|
||||
.on('text', (text) => {
|
||||
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) {
|
||||
resolve()
|
||||
return stream.controller.abort()
|
||||
}
|
||||
onChunk({ text })
|
||||
})
|
||||
.on('finalMessage', (message) => {
|
||||
onChunk({
|
||||
text: '',
|
||||
usage: {
|
||||
prompt_tokens: message.usage.input_tokens,
|
||||
completion_tokens: message.usage.output_tokens,
|
||||
total_tokens: sum(Object.values(message.usage))
|
||||
}
|
||||
})
|
||||
resolve()
|
||||
})
|
||||
.on('error', (error) => reject(error))
|
||||
})
|
||||
for await (const chunk of stream) {
|
||||
}
|
||||
|
||||
if (this.isGemini) {
|
||||
const geminiModel = this.geminiSdk.getGenerativeModel({
|
||||
model: model.id,
|
||||
systemInstruction: assistant.prompt,
|
||||
generationConfig: {
|
||||
maxOutputTokens: maxTokens,
|
||||
temperature: assistant?.settings?.temperature
|
||||
}
|
||||
})
|
||||
|
||||
const userLastMessage = userMessages.pop()
|
||||
|
||||
const chat = geminiModel.startChat({
|
||||
history: userMessages.map((message) => ({
|
||||
role: message.role === 'user' ? 'user' : 'model',
|
||||
parts: [{ text: message.content }]
|
||||
}))
|
||||
})
|
||||
|
||||
const userMessagesStream = await chat.sendMessageStream(userLastMessage?.content!)
|
||||
|
||||
for await (const chunk of userMessagesStream.stream) {
|
||||
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) break
|
||||
onChunk({ text: chunk.choices[0]?.delta?.content || '', usage: chunk.usage })
|
||||
onChunk({
|
||||
text: chunk.text(),
|
||||
usage: {
|
||||
prompt_tokens: chunk.usageMetadata?.promptTokenCount || 0,
|
||||
completion_tokens: chunk.usageMetadata?.candidatesTokenCount || 0,
|
||||
total_tokens: chunk.usageMetadata?.totalTokenCount || 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-ignore key is not typed
|
||||
const stream = await this.openaiSdk.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
|
||||
stream: true,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
max_tokens: maxTokens,
|
||||
keep_alive: this.keepAliveTime
|
||||
})
|
||||
|
||||
for await (const chunk of stream) {
|
||||
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) break
|
||||
onChunk({ text: chunk.choices[0]?.delta?.content || '', usage: chunk.usage })
|
||||
}
|
||||
}
|
||||
|
||||
public async translate(message: Message, assistant: Assistant) {
|
||||
const defaultModel = getDefaultModel()
|
||||
const { maxTokens } = getAssistantSettings(assistant)
|
||||
const model = assistant.model || defaultModel
|
||||
const messages = [
|
||||
{ role: 'system', content: assistant.prompt },
|
||||
@@ -94,29 +152,47 @@ export default class ProviderSDK {
|
||||
if (this.isAnthropic) {
|
||||
const response = await this.anthropicSdk.messages.create({
|
||||
model: model.id,
|
||||
messages: messages as MessageParam[],
|
||||
messages: messages.filter((m) => m.role === 'user') as MessageParam[],
|
||||
max_tokens: 4096,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
system: assistant.prompt,
|
||||
stream: false
|
||||
})
|
||||
|
||||
return response.content[0].type === 'text' ? response.content[0].text : ''
|
||||
} else {
|
||||
// @ts-ignore key is not typed
|
||||
const response = await this.openaiSdk.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: messages as ChatCompletionMessageParam[],
|
||||
stream: false,
|
||||
keep_alive: this.keepAliveTime
|
||||
})
|
||||
return response.choices[0].message?.content || ''
|
||||
}
|
||||
|
||||
if (this.isGemini) {
|
||||
const geminiModel = this.geminiSdk.getGenerativeModel({
|
||||
model: model.id,
|
||||
systemInstruction: assistant.prompt,
|
||||
generationConfig: {
|
||||
maxOutputTokens: maxTokens,
|
||||
temperature: assistant?.settings?.temperature
|
||||
}
|
||||
})
|
||||
|
||||
const { response } = await geminiModel.generateContent(message.content)
|
||||
|
||||
return response.text()
|
||||
}
|
||||
|
||||
// @ts-ignore key is not typed
|
||||
const response = await this.openaiSdk.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: messages as ChatCompletionMessageParam[],
|
||||
stream: false,
|
||||
keep_alive: this.keepAliveTime
|
||||
})
|
||||
|
||||
return response.choices[0].message?.content || ''
|
||||
}
|
||||
|
||||
public async summaries(messages: Message[], assistant: Assistant): Promise<string | null> {
|
||||
const model = getTopNamingModel() || assistant.model || getDefaultModel()
|
||||
|
||||
const userMessages = takeRight(messages, 5).map((message) => ({
|
||||
role: 'user',
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
|
||||
@@ -127,25 +203,49 @@ export default class ProviderSDK {
|
||||
|
||||
if (this.isAnthropic) {
|
||||
const message = await this.anthropicSdk.messages.create({
|
||||
messages: [systemMessage, ...userMessages] as Anthropic.Messages.MessageParam[],
|
||||
messages: userMessages as Anthropic.Messages.MessageParam[],
|
||||
model: model.id,
|
||||
system: systemMessage.content,
|
||||
stream: false,
|
||||
max_tokens: 50
|
||||
max_tokens: 4096
|
||||
})
|
||||
|
||||
return message.content[0].type === 'text' ? message.content[0].text : null
|
||||
} else {
|
||||
// @ts-ignore key is not typed
|
||||
const response = await this.openaiSdk.chat.completions.create({
|
||||
}
|
||||
|
||||
if (this.isGemini) {
|
||||
const geminiModel = this.geminiSdk.getGenerativeModel({
|
||||
model: model.id,
|
||||
messages: [systemMessage, ...userMessages] as ChatCompletionMessageParam[],
|
||||
stream: false,
|
||||
max_tokens: 50,
|
||||
keep_alive: this.keepAliveTime
|
||||
systemInstruction: systemMessage.content,
|
||||
generationConfig: {
|
||||
temperature: assistant?.settings?.temperature
|
||||
}
|
||||
})
|
||||
|
||||
return removeQuotes(response.choices[0].message?.content || '')
|
||||
const lastUserMessage = userMessages.pop()
|
||||
|
||||
const chat = await geminiModel.startChat({
|
||||
history: userMessages.map((message) => ({
|
||||
role: message.role === 'user' ? 'user' : 'model',
|
||||
parts: [{ text: message.content }]
|
||||
}))
|
||||
})
|
||||
|
||||
const { response } = await chat.sendMessage(lastUserMessage?.content!)
|
||||
|
||||
return response.text()
|
||||
}
|
||||
|
||||
// @ts-ignore key is not typed
|
||||
const response = await this.openaiSdk.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: [systemMessage, ...userMessages] as ChatCompletionMessageParam[],
|
||||
stream: false,
|
||||
max_tokens: 50,
|
||||
keep_alive: this.keepAliveTime
|
||||
})
|
||||
|
||||
return removeQuotes(response.choices[0].message?.content || '')
|
||||
}
|
||||
|
||||
public async suggestions(messages: Message[], assistant: Assistant): Promise<Suggestion[]> {
|
||||
@@ -172,6 +272,7 @@ export default class ProviderSDK {
|
||||
|
||||
public async check(): Promise<{ valid: boolean; error: Error | null }> {
|
||||
const model = this.provider.models[0]
|
||||
|
||||
const body = {
|
||||
model: model.id,
|
||||
messages: [{ role: 'user', content: 'hi' }],
|
||||
@@ -182,13 +283,32 @@ export default class ProviderSDK {
|
||||
try {
|
||||
if (this.isAnthropic) {
|
||||
const message = await this.anthropicSdk.messages.create(body as MessageCreateParamsNonStreaming)
|
||||
return { valid: message.content.length > 0, error: null }
|
||||
} else {
|
||||
const response = await this.openaiSdk.chat.completions.create(body as ChatCompletionCreateParamsNonStreaming)
|
||||
return { valid: Boolean(response?.choices[0].message), error: null }
|
||||
return {
|
||||
valid: message.content.length > 0,
|
||||
error: null
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isGemini) {
|
||||
const geminiModel = this.geminiSdk.getGenerativeModel({ model: body.model })
|
||||
const result = await geminiModel.generateContent(body.messages[0].content)
|
||||
return {
|
||||
valid: !isEmpty(result.response.text()),
|
||||
error: null
|
||||
}
|
||||
}
|
||||
|
||||
const response = await this.openaiSdk.chat.completions.create(body as ChatCompletionCreateParamsNonStreaming)
|
||||
|
||||
return {
|
||||
valid: Boolean(response?.choices[0].message),
|
||||
error: null
|
||||
}
|
||||
} catch (error: any) {
|
||||
return { valid: false, error }
|
||||
return {
|
||||
valid: false,
|
||||
error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +318,22 @@ export default class ProviderSDK {
|
||||
return []
|
||||
}
|
||||
|
||||
if (this.isGemini) {
|
||||
const api = this.provider.apiHost + '/v1beta/models'
|
||||
const { data } = await axios.get(api, { params: { key: this.provider.apiKey } })
|
||||
return data.models.map(
|
||||
(m: any) =>
|
||||
({
|
||||
id: m.name.replace('models/', ''),
|
||||
name: m.displayName,
|
||||
description: m.description,
|
||||
object: 'model',
|
||||
created: Date.now(),
|
||||
owned_by: 'gemini'
|
||||
}) as OpenAI.Models.Model
|
||||
)
|
||||
}
|
||||
|
||||
const response = await this.openaiSdk.models.list()
|
||||
return response.data
|
||||
} catch (error) {
|
||||
|
||||
@@ -22,7 +22,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 20,
|
||||
version: 21,
|
||||
blacklist: ['runtime'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@@ -31,6 +31,15 @@ const initialState: LlmState = {
|
||||
isSystem: true,
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 'gemini',
|
||||
name: 'Gemini',
|
||||
apiKey: '',
|
||||
apiHost: 'https://generativelanguage.googleapis.com',
|
||||
models: SYSTEM_MODELS.gemini.filter((m) => m.enabled),
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'silicon',
|
||||
name: 'Silicon',
|
||||
@@ -95,11 +104,20 @@ const initialState: LlmState = {
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
id: 'stepfun',
|
||||
name: 'StepFun',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.anthropic.com/',
|
||||
models: SYSTEM_MODELS.anthropic.filter((m) => m.enabled),
|
||||
apiHost: 'https://api.stepfun.com',
|
||||
models: SYSTEM_MODELS.stepfun.filter((m) => m.enabled),
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'doubao',
|
||||
name: 'doubao',
|
||||
apiKey: '',
|
||||
apiHost: 'https://ark.cn-beijing.volces.com/api/v3/',
|
||||
models: SYSTEM_MODELS.doubao.filter((m) => m.enabled),
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
@@ -112,6 +130,15 @@ const initialState: LlmState = {
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'graphrag-kylin-mountain',
|
||||
name: 'GraphRAG',
|
||||
apiKey: '',
|
||||
apiHost: '',
|
||||
models: [],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
@@ -130,6 +157,15 @@ const initialState: LlmState = {
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.anthropic.com/',
|
||||
models: SYSTEM_MODELS.anthropic.filter((m) => m.enabled),
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'ollama',
|
||||
name: 'Ollama',
|
||||
|
||||
@@ -296,6 +296,53 @@ const migrateConfig = {
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
},
|
||||
'21': (state: RootState) => {
|
||||
return {
|
||||
...state,
|
||||
llm: {
|
||||
...state.llm,
|
||||
providers: [
|
||||
...state.llm.providers,
|
||||
{
|
||||
id: 'gemini',
|
||||
name: 'Gemini',
|
||||
apiKey: '',
|
||||
apiHost: 'https://generativelanguage.googleapis.com',
|
||||
models: SYSTEM_MODELS.gemini.filter((m) => m.enabled),
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'stepfun',
|
||||
name: 'StepFun',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.stepfun.com',
|
||||
models: SYSTEM_MODELS.stepfun.filter((m) => m.enabled),
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'doubao',
|
||||
name: 'doubao',
|
||||
apiKey: '',
|
||||
apiHost: 'https://ark.cn-beijing.volces.com/api/v3/',
|
||||
models: SYSTEM_MODELS.doubao.filter((m) => m.enabled),
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'graphrag-kylin-mountain',
|
||||
name: 'GraphRAG',
|
||||
apiKey: '',
|
||||
apiHost: '',
|
||||
models: [],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ import Logo from '@renderer/assets/images/logo.png'
|
||||
export interface RuntimeState {
|
||||
avatar: string
|
||||
generating: boolean
|
||||
minappShow: boolean
|
||||
}
|
||||
|
||||
const initialState: RuntimeState = {
|
||||
avatar: Logo,
|
||||
generating: false
|
||||
generating: false,
|
||||
minappShow: false
|
||||
}
|
||||
|
||||
const runtimeSlice = createSlice({
|
||||
@@ -20,10 +22,13 @@ const runtimeSlice = createSlice({
|
||||
},
|
||||
setGenerating: (state, action: PayloadAction<boolean>) => {
|
||||
state.generating = action.payload
|
||||
},
|
||||
setMinappShow: (state, action: PayloadAction<boolean>) => {
|
||||
state.minappShow = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { setAvatar, setGenerating } = runtimeSlice.actions
|
||||
export const { setAvatar, setGenerating, setMinappShow } = runtimeSlice.actions
|
||||
|
||||
export default runtimeSlice.reducer
|
||||
|
||||
89
yarn.lock
@@ -961,6 +961,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@google/generative-ai@npm:^0.16.0":
|
||||
version: 0.16.0
|
||||
resolution: "@google/generative-ai@npm:0.16.0"
|
||||
checksum: 10c0/5d561a41cb7be60fc9b49965b66359e15df907bf6679009de7917beff138ba69d4a0772ab2a9d6f0e543d658d72bd19b83e6abdb87a6cdfa402a8764b08eed4c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@hello-pangea/dnd@npm:^16.6.0":
|
||||
version: 16.6.0
|
||||
resolution: "@hello-pangea/dnd@npm:16.6.0"
|
||||
@@ -2698,25 +2705,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ahooks@npm:^3.8.0":
|
||||
version: 3.8.0
|
||||
resolution: "ahooks@npm:3.8.0"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.21.0"
|
||||
dayjs: "npm:^1.9.1"
|
||||
intersection-observer: "npm:^0.12.0"
|
||||
js-cookie: "npm:^2.x.x"
|
||||
lodash: "npm:^4.17.21"
|
||||
react-fast-compare: "npm:^3.2.2"
|
||||
resize-observer-polyfill: "npm:^1.5.1"
|
||||
screenfull: "npm:^5.0.0"
|
||||
tslib: "npm:^2.4.1"
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: 10c0/28344a10443a1374066d931ac687e8f18f714717c32fc4c12b5fea1d0b29b9dfbddd5e9943df2394dd7912440e474413d899e1a2877bf80093fbe8f1c3b5ea58
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ajv-formats@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "ajv-formats@npm:2.1.1"
|
||||
@@ -3099,6 +3087,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.7.3":
|
||||
version: 1.7.3
|
||||
resolution: "axios@npm:1.7.3"
|
||||
dependencies:
|
||||
follow-redirects: "npm:^1.15.6"
|
||||
form-data: "npm:^4.0.0"
|
||||
proxy-from-env: "npm:^1.1.0"
|
||||
checksum: 10c0/a18cbe559203efa05fb1fec2d1898e23bf6329bd2575784ee32aa11b5bbe1d54b9f472c49a261294125519cf62aa4fe5ef6e647bb7482eafc15bffe15ab314ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bail@npm:^2.0.0":
|
||||
version: 2.0.2
|
||||
resolution: "bail@npm:2.0.2"
|
||||
@@ -3446,6 +3445,7 @@ __metadata:
|
||||
"@electron-toolkit/preload": "npm:^3.0.0"
|
||||
"@electron-toolkit/tsconfig": "npm:^1.0.1"
|
||||
"@electron-toolkit/utils": "npm:^3.0.0"
|
||||
"@google/generative-ai": "npm:^0.16.0"
|
||||
"@hello-pangea/dnd": "npm:^16.6.0"
|
||||
"@kangfenmao/keyv-storage": "npm:^0.1.0"
|
||||
"@reduxjs/toolkit": "npm:^2.2.5"
|
||||
@@ -3455,8 +3455,8 @@ __metadata:
|
||||
"@types/react": "npm:^18.2.48"
|
||||
"@types/react-dom": "npm:^18.2.18"
|
||||
"@vitejs/plugin-react": "npm:^4.2.1"
|
||||
ahooks: "npm:^3.8.0"
|
||||
antd: "npm:^5.18.3"
|
||||
axios: "npm:^1.7.3"
|
||||
browser-image-compression: "npm:^2.0.2"
|
||||
dayjs: "npm:^1.11.11"
|
||||
dotenv-cli: "npm:^7.4.2"
|
||||
@@ -3831,7 +3831,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dayjs@npm:^1.11.11, dayjs@npm:^1.9.1":
|
||||
"dayjs@npm:^1.11.11":
|
||||
version: 1.11.11
|
||||
resolution: "dayjs@npm:1.11.11"
|
||||
checksum: 10c0/0131d10516b9945f05a57e13f4af49a6814de5573a494824e103131a3bbe4cc470b1aefe8e17e51f9a478a22cd116084be1ee5725cedb66ec4c3f9091202dc4b
|
||||
@@ -5037,6 +5037,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"follow-redirects@npm:^1.15.6":
|
||||
version: 1.15.6
|
||||
resolution: "follow-redirects@npm:1.15.6"
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"for-each@npm:^0.3.3":
|
||||
version: 0.3.3
|
||||
resolution: "for-each@npm:0.3.3"
|
||||
@@ -5876,13 +5886,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"intersection-observer@npm:^0.12.0":
|
||||
version: 0.12.2
|
||||
resolution: "intersection-observer@npm:0.12.2"
|
||||
checksum: 10c0/9591f46b2b742f5801ed69dbc8860f487771b4af8361e7a5dcb28a377beff2ba56336a2b090af261825430d225dae9417121496d2e6925e000e4a469958843ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ip-address@npm:^9.0.5":
|
||||
version: 9.0.5
|
||||
resolution: "ip-address@npm:9.0.5"
|
||||
@@ -6320,13 +6323,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-cookie@npm:^2.x.x":
|
||||
version: 2.2.1
|
||||
resolution: "js-cookie@npm:2.2.1"
|
||||
checksum: 10c0/ee67fc0f8495d0800b851910b5eb5bf49d3033adff6493d55b5c097ca6da46f7fe666b10e2ecb13cfcaf5b88d71c205ce00a7e646de791689bfd053bbb36a376
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-tiktoken@npm:^1.0.10, js-tiktoken@npm:^1.0.7":
|
||||
version: 1.0.12
|
||||
resolution: "js-tiktoken@npm:1.0.12"
|
||||
@@ -8251,6 +8247,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proxy-from-env@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "proxy-from-env@npm:1.1.0"
|
||||
checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pump@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "pump@npm:3.0.0"
|
||||
@@ -8844,13 +8847,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-fast-compare@npm:^3.2.2":
|
||||
version: 3.2.2
|
||||
resolution: "react-fast-compare@npm:3.2.2"
|
||||
checksum: 10c0/0bbd2f3eb41ab2ff7380daaa55105db698d965c396df73e6874831dbafec8c4b5b08ba36ff09df01526caa3c61595247e3269558c284e37646241cba2b90a367
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-i18next@npm:^14.1.2":
|
||||
version: 14.1.2
|
||||
resolution: "react-i18next@npm:14.1.2"
|
||||
@@ -9514,13 +9510,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"screenfull@npm:^5.0.0":
|
||||
version: 5.2.0
|
||||
resolution: "screenfull@npm:5.2.0"
|
||||
checksum: 10c0/86fd49983e2edc153ee2e674a570c711cb0961a9cacca659309f79636ccc8ca8a0b830ea4dacdae7403a8bb7ba6affd5bcdce053aa97782961247a49bfd2ba68
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"scroll-into-view-if-needed@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "scroll-into-view-if-needed@npm:3.1.0"
|
||||
@@ -10106,7 +10095,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2.1.0, tslib@npm:^2.4.1":
|
||||
"tslib@npm:^2.1.0":
|
||||
version: 2.6.3
|
||||
resolution: "tslib@npm:2.6.3"
|
||||
checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a
|
||||
|
||||