Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d7cde1231 | ||
|
|
87c04408de | ||
|
|
2592448c74 | ||
|
|
6f054874e8 | ||
|
|
40d687104e | ||
|
|
ac3cfe2878 | ||
|
|
e9a7735fce | ||
|
|
c1a8198575 | ||
|
|
8b45548b79 | ||
|
|
3f3b930819 | ||
|
|
a5d6e2c5c5 | ||
|
|
2993ab8dc1 | ||
|
|
117069e450 | ||
|
|
c5965dc696 | ||
|
|
4169a2ef35 | ||
|
|
75c37632d4 | ||
|
|
3f5c151a11 | ||
|
|
d049e36c46 | ||
|
|
d05fc1c9be | ||
|
|
f33317a3fb | ||
|
|
f2b5ed09c0 |
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
# Feature
|
# Feature
|
||||||
|
|
||||||
|
|||||||
@@ -56,4 +56,6 @@ electronDownload:
|
|||||||
afterSign: scripts/notarize.js
|
afterSign: scripts/notarize.js
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
📢 新功能:可以添加自定义服务提供商了
|
支持设置模型 Temperature 参数
|
||||||
|
支持设置上下文数量
|
||||||
|
输入框增加 Token 消耗预估
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cherry-studio",
|
"name": "cherry-studio",
|
||||||
"version": "0.2.8",
|
"version": "0.3.2",
|
||||||
"description": "A powerful AI assistant for producer.",
|
"description": "A powerful AI assistant for producer.",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "kangfenmao@qq.com",
|
"author": "kangfenmao@qq.com",
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"eslint-plugin-react": "^7.34.3",
|
"eslint-plugin-react": "^7.34.3",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-unused-imports": "^4.0.0",
|
"eslint-plugin-unused-imports": "^4.0.0",
|
||||||
|
"gpt-tokens": "^1.3.6",
|
||||||
"i18next": "^23.11.5",
|
"i18next": "^23.11.5",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
# CHANGES LOG
|
|
||||||
|
|
||||||
### v0.2.8 - 2024-07-20
|
|
||||||
|
|
||||||
- 🆕 Feature: Add customized service providers
|
|
||||||
|
|
||||||
### v0.2.7 - 2024-07-19
|
|
||||||
|
|
||||||
- 📢 Add DashScope Provider
|
|
||||||
- 📢 Add Anthropic Provider
|
|
||||||
|
|
||||||
### v0.2.6 - 2024-07-17
|
|
||||||
|
|
||||||
- 🆕 Fixed the issue of the BaiChuan API KEY not displaying when clicking to obtain the URL
|
|
||||||
- 📢 New intelligent body center style
|
|
||||||
|
|
||||||
### v0.2.5 - 2024-07-17
|
|
||||||
|
|
||||||
- 🆕 Baichuan AI Service Providers
|
|
||||||
- 📢 New Intelligent Agent Page with Multiple Professional Assistants
|
|
||||||
- 🌐 Multilingual Issue Fixes and Detailed Optimizations
|
|
||||||
|
|
||||||
### v0.2.4 - 2024-07-16
|
|
||||||
|
|
||||||
- Fixed the issue of the update log page cannot be scrolled
|
|
||||||
- Added a check for updates button
|
|
||||||
|
|
||||||
### v0.2.3 - 2024-07-16
|
|
||||||
|
|
||||||
- Fixed multi-language prompt errors
|
|
||||||
- Fixed default model error issues with ZHIPU AI
|
|
||||||
- Fixed OpenRouter API detection error issues
|
|
||||||
- Fixed multi-language translation errors with model providers
|
|
||||||
|
|
||||||
### v0.2.2 - 2024-07-15
|
|
||||||
|
|
||||||
- Fix the issue where the default assistant name is empty.
|
|
||||||
- Fix the problem with default language detection during the first installation.
|
|
||||||
- Adjust the changelog style.
|
|
||||||
|
|
||||||
### v0.2.1 - 2024-07-15
|
|
||||||
|
|
||||||
- **Feature**: Add new feature for pausing message sending
|
|
||||||
- **Fix**: Resolve incomplete translation issue upon language switch
|
|
||||||
- **Build**: Support for macOS Intel architecture
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# 更新日志
|
|
||||||
|
|
||||||
### v0.2.8 - 2024-07-20
|
|
||||||
|
|
||||||
- 🆕 新功能: 可以添加自定义服务提供商了
|
|
||||||
|
|
||||||
### v0.2.7 - 2024-07-19
|
|
||||||
|
|
||||||
- 📢 新增阿里云灵积服务商
|
|
||||||
- 📢 新增 Anthropic 服务商
|
|
||||||
|
|
||||||
### v0.2.6 - 2024-07-17
|
|
||||||
|
|
||||||
- 🆕 修复百川 API KEY 点击获取网址没有显示问题
|
|
||||||
- 📢 新的智能体中心样式
|
|
||||||
|
|
||||||
### v0.2.5 - 2024-07-17
|
|
||||||
|
|
||||||
- 🆕 新增百川AI服务商
|
|
||||||
- 📢 全新的智能体页面,新增多种职业助手
|
|
||||||
- 🌐 多语言问题修复,细节优化
|
|
||||||
|
|
||||||
### v0.2.4 - 2024-07-16
|
|
||||||
|
|
||||||
- 修复更新日志页面不能滚动问题
|
|
||||||
- 新增检查更新按钮
|
|
||||||
|
|
||||||
### v0.2.3 - 2024-07-16
|
|
||||||
|
|
||||||
- 修复多语言提示错误
|
|
||||||
- 修复智谱AI默认模型错误问题
|
|
||||||
- 修复 OpenRouter API 检测出错问题
|
|
||||||
- 修复模型提供商多语言翻译错误问题
|
|
||||||
|
|
||||||
### v0.2.2 - 2024-07-15
|
|
||||||
|
|
||||||
- 修复默认助理名称为空的问题
|
|
||||||
- 修复首次安装默认语言检测问题
|
|
||||||
- 更新日志样式微调
|
|
||||||
|
|
||||||
### v0.2.1 - 2024-07-15
|
|
||||||
|
|
||||||
- 【功能】新增消息暂停发送功能
|
|
||||||
- 【修复】修复多语言切换不彻底问题
|
|
||||||
- 【构建】支持 macOS Intel 架构
|
|
||||||
|
|
||||||
BIN
src/renderer/src/assets/images/providers/aihubmix.jpg
Normal file
BIN
src/renderer/src/assets/images/providers/aihubmix.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -38,7 +38,7 @@
|
|||||||
--topic-list-width: 250px;
|
--topic-list-width: 250px;
|
||||||
--settings-width: var(--assistants-width);
|
--settings-width: var(--assistants-width);
|
||||||
--status-bar-height: 40px;
|
--status-bar-height: 40px;
|
||||||
--input-bar-height: 120px;
|
--input-bar-height: 125px;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useAppInitEffect } from '@renderer/hooks/useAppInitEffect'
|
import { useAppInit } from '@renderer/hooks/useAppInit'
|
||||||
import { message, Modal } from 'antd'
|
import { message, Modal } from 'antd'
|
||||||
import { findIndex, pullAt } from 'lodash'
|
import { findIndex, pullAt } from 'lodash'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
@@ -29,7 +29,7 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
|
|||||||
const [messageApi, messageContextHolder] = message.useMessage()
|
const [messageApi, messageContextHolder] = message.useMessage()
|
||||||
const [modal, modalContextHolder] = Modal.useModal()
|
const [modal, modalContextHolder] = Modal.useModal()
|
||||||
|
|
||||||
useAppInitEffect()
|
useAppInit()
|
||||||
|
|
||||||
onPop = () => {
|
onPop = () => {
|
||||||
const views = [...elements]
|
const views = [...elements]
|
||||||
|
|||||||
@@ -44,14 +44,13 @@ const Container = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 0;
|
padding: 8px 0;
|
||||||
min-width: var(--sidebar-width);
|
min-width: var(--sidebar-width);
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
-webkit-app-region: drag !important;
|
-webkit-app-region: drag !important;
|
||||||
background-color: #1f1f1f;
|
background-color: #1f1f1f;
|
||||||
border-right: 0.5px solid var(--color-border);
|
border-right: 0.5px solid var(--color-border);
|
||||||
margin-top: var(--navbar-height);
|
padding-top: var(--navbar-height);
|
||||||
padding-bottom: calc(var(--navbar-height) + 6px);
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const AvatarImg = styled.img`
|
const AvatarImg = styled.img`
|
||||||
@@ -60,6 +59,7 @@ const AvatarImg = styled.img`
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
|
margin-top: 12px;
|
||||||
`
|
`
|
||||||
const MainMenus = styled.div`
|
const MainMenus = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export const DEFAULT_TEMPERATURE = 0.7
|
||||||
|
export const DEFAULT_CONEXTCOUNT = 5
|
||||||
|
|||||||
@@ -320,6 +320,22 @@ export const SYSTEM_MODELS: Record<string, SystemModel[]> = {
|
|||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
aihubmix: [
|
||||||
|
{
|
||||||
|
id: 'gpt-4o-mini',
|
||||||
|
provider: 'aihubmix',
|
||||||
|
name: 'GPT-4o Mini',
|
||||||
|
group: 'GPT-4o',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aihubmix-Llama-3-70B-Instruct',
|
||||||
|
provider: 'aihubmix',
|
||||||
|
name: 'Llama 3 70B Instruct',
|
||||||
|
group: 'Llama3',
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
],
|
||||||
openrouter: [
|
openrouter: [
|
||||||
{
|
{
|
||||||
id: 'google/gemma-2-9b-it:free',
|
id: 'google/gemma-2-9b-it:free',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import OpenRouterProviderLogo from '@renderer/assets/images/providers/openrouter
|
|||||||
import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png'
|
import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png'
|
||||||
import DashScopeProviderLogo from '@renderer/assets/images/providers/dashscope.png'
|
import DashScopeProviderLogo from '@renderer/assets/images/providers/dashscope.png'
|
||||||
import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.jpeg'
|
import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.jpeg'
|
||||||
|
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.jpg'
|
||||||
import ChatGPTModelLogo from '@renderer/assets/images/models/chatgpt.jpeg'
|
import ChatGPTModelLogo from '@renderer/assets/images/models/chatgpt.jpeg'
|
||||||
import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.jpeg'
|
import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.jpeg'
|
||||||
import DeepSeekModelLogo from '@renderer/assets/images/models/deepseek.png'
|
import DeepSeekModelLogo from '@renderer/assets/images/models/deepseek.png'
|
||||||
@@ -49,6 +50,8 @@ export function getProviderLogo(providerId: string) {
|
|||||||
return DashScopeProviderLogo
|
return DashScopeProviderLogo
|
||||||
case 'anthropic':
|
case 'anthropic':
|
||||||
return AnthropicProviderLogo
|
return AnthropicProviderLogo
|
||||||
|
case 'aihubmix':
|
||||||
|
return AiHubMixProviderLogo
|
||||||
default:
|
default:
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -175,5 +178,13 @@ export const PROVIDER_CONFIG = {
|
|||||||
docs: 'https://docs.anthropic.com/en/docs',
|
docs: 'https://docs.anthropic.com/en/docs',
|
||||||
models: 'https://docs.anthropic.com/en/docs/about-claude/models'
|
models: 'https://docs.anthropic.com/en/docs/about-claude/models'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
aihubmix: {
|
||||||
|
websites: {
|
||||||
|
official: 'https://aihubmix.com/',
|
||||||
|
apiKey: 'https://aihubmix.com/token',
|
||||||
|
docs: 'https://doc.aihubmix.com/',
|
||||||
|
models: 'https://aihubmix.com/models'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { setAvatar } from '@renderer/store/runtime'
|
|||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { runAsyncFunction } from '@renderer/utils'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
export function useAppInitEffect() {
|
export function useAppInit() {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import {
|
import {
|
||||||
addTopic as _addTopic,
|
|
||||||
removeAllTopics as _removeAllTopics,
|
|
||||||
removeTopic as _removeTopic,
|
|
||||||
setModel as _setModel,
|
|
||||||
updateAssistants as _updateAssistants,
|
|
||||||
updateDefaultAssistant as _updateDefaultAssistant,
|
|
||||||
updateTopic as _updateTopic,
|
|
||||||
updateTopics as _updateTopics,
|
|
||||||
addAssistant,
|
addAssistant,
|
||||||
|
addTopic,
|
||||||
|
removeAllTopics,
|
||||||
removeAssistant,
|
removeAssistant,
|
||||||
updateAssistant
|
removeTopic,
|
||||||
|
setModel,
|
||||||
|
updateAssistant,
|
||||||
|
updateAssistants,
|
||||||
|
updateAssistantSettings,
|
||||||
|
updateDefaultAssistant,
|
||||||
|
updateTopic,
|
||||||
|
updateTopics
|
||||||
} from '@renderer/store/assistants'
|
} from '@renderer/store/assistants'
|
||||||
import { setDefaultModel as _setDefaultModel, setTopicNamingModel as _setTopicNamingModel } from '@renderer/store/llm'
|
import { setDefaultModel as _setDefaultModel, setTopicNamingModel as _setTopicNamingModel } from '@renderer/store/llm'
|
||||||
import { Assistant, Model, Topic } from '@renderer/types'
|
import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
|
|
||||||
export function useAssistants() {
|
export function useAssistants() {
|
||||||
@@ -23,9 +24,8 @@ export function useAssistants() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
assistants,
|
assistants,
|
||||||
updateAssistants: (assistants: Assistant[]) => dispatch(_updateAssistants(assistants)),
|
updateAssistants: (assistants: Assistant[]) => dispatch(updateAssistants(assistants)),
|
||||||
addAssistant: (assistant: Assistant) => dispatch(addAssistant(assistant)),
|
addAssistant: (assistant: Assistant) => dispatch(addAssistant(assistant)),
|
||||||
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
|
|
||||||
removeAssistant: (id: string) => {
|
removeAssistant: (id: string) => {
|
||||||
dispatch(removeAssistant({ id }))
|
dispatch(removeAssistant({ id }))
|
||||||
const assistant = assistants.find((a) => a.id === id)
|
const assistant = assistants.find((a) => a.id === id)
|
||||||
@@ -44,17 +44,21 @@ export function useAssistant(id: string) {
|
|||||||
return {
|
return {
|
||||||
assistant,
|
assistant,
|
||||||
model: assistant?.model ?? defaultModel,
|
model: assistant?.model ?? defaultModel,
|
||||||
addTopic: (topic: Topic) => dispatch(_addTopic({ assistantId: assistant.id, topic })),
|
addTopic: (topic: Topic) => dispatch(addTopic({ assistantId: assistant.id, topic })),
|
||||||
removeTopic: (topic: Topic) => dispatch(_removeTopic({ assistantId: assistant.id, topic })),
|
removeTopic: (topic: Topic) => dispatch(removeTopic({ assistantId: assistant.id, topic })),
|
||||||
updateTopic: (topic: Topic) => dispatch(_updateTopic({ assistantId: assistant.id, topic })),
|
updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })),
|
||||||
updateTopics: (topics: Topic[]) => dispatch(_updateTopics({ assistantId: assistant.id, topics })),
|
updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })),
|
||||||
removeAllTopics: () => dispatch(_removeAllTopics({ assistantId: assistant.id })),
|
removeAllTopics: () => dispatch(removeAllTopics({ assistantId: assistant.id })),
|
||||||
setModel: (model: Model) => dispatch(_setModel({ assistantId: assistant.id, model }))
|
setModel: (model: Model) => dispatch(setModel({ assistantId: assistant.id, model })),
|
||||||
|
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
|
||||||
|
updateAssistantSettings: (settings: AssistantSettings) => {
|
||||||
|
dispatch(updateAssistantSettings({ assistantId: assistant.id, settings }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDefaultAssistant() {
|
export function useDefaultAssistant() {
|
||||||
const { defaultAssistant } = useAppSelector((state) => state.assistants)
|
const defaultAssistant = useAppSelector((state) => state.assistants.defaultAssistant)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -62,7 +66,7 @@ export function useDefaultAssistant() {
|
|||||||
...defaultAssistant,
|
...defaultAssistant,
|
||||||
topics: [getDefaultTopic()]
|
topics: [getDefaultTopic()]
|
||||||
},
|
},
|
||||||
updateDefaultAssistant: (assistant: Assistant) => dispatch(_updateDefaultAssistant({ assistant }))
|
updateDefaultAssistant: (assistant: Assistant) => dispatch(updateDefaultAssistant({ assistant }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import {
|
import {
|
||||||
addModel as _addModel,
|
addModel,
|
||||||
removeModel as _removeModel,
|
|
||||||
updateProvider as _updateProvider,
|
|
||||||
updateProviders as _updateProviders,
|
|
||||||
addProvider,
|
addProvider,
|
||||||
removeProvider
|
removeModel,
|
||||||
|
removeProvider,
|
||||||
|
updateProvider,
|
||||||
|
updateProviders
|
||||||
} from '@renderer/store/llm'
|
} from '@renderer/store/llm'
|
||||||
import { Assistant, Model, Provider } from '@renderer/types'
|
import { Assistant, Model, Provider } from '@renderer/types'
|
||||||
import { useDefaultModel } from './useAssistant'
|
import { useDefaultModel } from './useAssistant'
|
||||||
|
import { createSelector } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
const selectEnabledProviders = createSelector(
|
||||||
|
(state) => state.llm.providers,
|
||||||
|
(providers) => providers.filter((p) => p.enabled)
|
||||||
|
)
|
||||||
|
|
||||||
export function useProviders() {
|
export function useProviders() {
|
||||||
const providers = useAppSelector((state) => state.llm.providers.filter((p) => p.enabled))
|
const providers = useAppSelector(selectEnabledProviders)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
providers,
|
providers,
|
||||||
addProvider: (provider: Provider) => dispatch(addProvider(provider)),
|
addProvider: (provider: Provider) => dispatch(addProvider(provider)),
|
||||||
removeProvider: (provider: Provider) => dispatch(removeProvider(provider)),
|
removeProvider: (provider: Provider) => dispatch(removeProvider(provider)),
|
||||||
updateProvider: (provider: Provider) => dispatch(_updateProvider(provider)),
|
updateProvider: (provider: Provider) => dispatch(updateProvider(provider)),
|
||||||
updateProviders: (providers: Provider[]) => dispatch(_updateProviders(providers))
|
updateProviders: (providers: Provider[]) => dispatch(updateProviders(providers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +48,9 @@ export function useProvider(id: string) {
|
|||||||
return {
|
return {
|
||||||
provider,
|
provider,
|
||||||
models: provider.models,
|
models: provider.models,
|
||||||
updateProvider: (provider: Provider) => dispatch(_updateProvider(provider)),
|
updateProvider: (provider: Provider) => dispatch(updateProvider(provider)),
|
||||||
addModel: (model: Model) => dispatch(_addModel({ providerId: id, model })),
|
addModel: (model: Model) => dispatch(addModel({ providerId: id, model })),
|
||||||
removeModel: (model: Model) => dispatch(_removeModel({ providerId: id, model }))
|
removeModel: (model: Model) => dispatch(removeModel({ providerId: id, model }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ const resources = {
|
|||||||
'error.enter.model': 'Please select a model first',
|
'error.enter.model': 'Please select a model first',
|
||||||
'api.connection.failed': 'Connection failed',
|
'api.connection.failed': 'Connection failed',
|
||||||
'api.connection.success': 'Connection successful',
|
'api.connection.success': 'Connection successful',
|
||||||
'chat.completion.paused': 'Chat completion paused'
|
'chat.completion.paused': 'Chat completion paused',
|
||||||
|
'switch.disabled': 'Switching is disabled while the assistant is generating'
|
||||||
},
|
},
|
||||||
assistant: {
|
assistant: {
|
||||||
'default.name': 'Default Assistant',
|
'default.name': '😀 Default Assistant',
|
||||||
'default.description': "Hello, I'm Default Assistant. You can start chatting with me right away",
|
'default.description': "Hello, I'm Default Assistant. You can start chatting with me right away",
|
||||||
'default.topic.name': 'Default Topic',
|
'default.topic.name': 'Default Topic',
|
||||||
'topics.title': 'Topics',
|
'topics.title': 'Topics',
|
||||||
@@ -64,7 +65,17 @@ const resources = {
|
|||||||
'input.clear.content': 'Are you sure to clear all messages?',
|
'input.clear.content': 'Are you sure to clear all messages?',
|
||||||
'input.placeholder': 'Type your message here...',
|
'input.placeholder': 'Type your message here...',
|
||||||
'input.send': 'Send',
|
'input.send': 'Send',
|
||||||
'input.pause': 'Pause'
|
'input.pause': 'Pause',
|
||||||
|
'input.settings': 'Settings',
|
||||||
|
'input.estimated_tokens': 'Estimated Tokens: ',
|
||||||
|
'settings.temperature': 'Temperature',
|
||||||
|
'settings.temperature.tip':
|
||||||
|
'Lower values make the model more creative and unpredictable, while higher values make it more deterministic and precise.',
|
||||||
|
'settings.conext_count': 'Context',
|
||||||
|
'settings.conext_count.tip': 'The number of previous messages to keep in the context.',
|
||||||
|
'settings.reset': 'Reset',
|
||||||
|
'settings.set_as_default': 'Apply to default assistant',
|
||||||
|
'settings.max': 'Max'
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: 'Agents'
|
title: 'Agents'
|
||||||
@@ -81,15 +92,16 @@ const resources = {
|
|||||||
ollama: 'Ollama',
|
ollama: 'Ollama',
|
||||||
baichuan: 'Baichuan',
|
baichuan: 'Baichuan',
|
||||||
dashscope: 'DashScope',
|
dashscope: 'DashScope',
|
||||||
anthropic: 'Anthropic'
|
anthropic: 'Anthropic',
|
||||||
|
aihubmix: 'AiHubMix'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
general: 'General',
|
general: 'General Settings',
|
||||||
provider: 'Model Provider',
|
provider: 'Model Provider',
|
||||||
model: 'Model Settings',
|
model: 'Model Settings',
|
||||||
assistant: 'Default Assistant',
|
assistant: 'Default Assistant',
|
||||||
about: 'About',
|
about: 'About & Feedback',
|
||||||
'general.title': 'General Settings',
|
'general.title': 'General Settings',
|
||||||
'provider.api_key': 'API Key',
|
'provider.api_key': 'API Key',
|
||||||
'provider.check': 'Check',
|
'provider.check': 'Check',
|
||||||
@@ -111,6 +123,7 @@ const resources = {
|
|||||||
'models.add.group_name.placeholder': 'Optional e.g. ChatGPT',
|
'models.add.group_name.placeholder': 'Optional e.g. ChatGPT',
|
||||||
'models.empty': 'No models found',
|
'models.empty': 'No models found',
|
||||||
'assistant.title': 'Default Assistant',
|
'assistant.title': 'Default Assistant',
|
||||||
|
'assistant.model_params': 'Model Parameters',
|
||||||
'about.description': 'A powerful AI assistant for producer',
|
'about.description': 'A powerful AI assistant for producer',
|
||||||
'about.updateNotAvailable': 'You are using the latest version',
|
'about.updateNotAvailable': 'You are using the latest version',
|
||||||
'about.checkingUpdate': 'Checking for updates...',
|
'about.checkingUpdate': 'Checking for updates...',
|
||||||
@@ -120,7 +133,16 @@ const resources = {
|
|||||||
'provider.delete.title': 'Delete Provider',
|
'provider.delete.title': 'Delete Provider',
|
||||||
'provider.delete.content': 'Are you sure you want to delete this provider?',
|
'provider.delete.content': 'Are you sure you want to delete this provider?',
|
||||||
'provider.edit.name': 'Provider Name',
|
'provider.edit.name': 'Provider Name',
|
||||||
'provider.edit.name.placeholder': 'Example: OpenAI'
|
'provider.edit.name.placeholder': 'Example: OpenAI',
|
||||||
|
'about.title': 'About',
|
||||||
|
'about.releases.title': '📔 Release Notes',
|
||||||
|
'about.releases.button': 'Releases',
|
||||||
|
'about.website.title': '🌐 Official Website',
|
||||||
|
'about.website.button': 'Website',
|
||||||
|
'about.feedback.title': '📝 Feedback',
|
||||||
|
'about.feedback.button': 'Feedback',
|
||||||
|
'about.contact.title': '📧 Contact',
|
||||||
|
'about.contact.button': 'Email'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -162,10 +184,11 @@ const resources = {
|
|||||||
'error.enter.model': '请选择一个模型',
|
'error.enter.model': '请选择一个模型',
|
||||||
'api.connection.failed': '连接失败',
|
'api.connection.failed': '连接失败',
|
||||||
'api.connection.success': '连接成功',
|
'api.connection.success': '连接成功',
|
||||||
'chat.completion.paused': '会话已停止'
|
'chat.completion.paused': '会话已停止',
|
||||||
|
'switch.disabled': '模型回复完成后才能切换'
|
||||||
},
|
},
|
||||||
assistant: {
|
assistant: {
|
||||||
'default.name': '默认助手',
|
'default.name': '😃 默认助手 - Assistant',
|
||||||
'default.description': '你好,我是默认助手。你可以立刻开始跟我聊天。',
|
'default.description': '你好,我是默认助手。你可以立刻开始跟我聊天。',
|
||||||
'default.topic.name': '默认话题',
|
'default.topic.name': '默认话题',
|
||||||
'topics.title': '话题',
|
'topics.title': '话题',
|
||||||
@@ -185,7 +208,18 @@ const resources = {
|
|||||||
'input.clear.content': '确定要清除所有消息吗?',
|
'input.clear.content': '确定要清除所有消息吗?',
|
||||||
'input.placeholder': '在这里输入消息...',
|
'input.placeholder': '在这里输入消息...',
|
||||||
'input.send': '发送',
|
'input.send': '发送',
|
||||||
'input.pause': '暂停'
|
'input.pause': '暂停',
|
||||||
|
'input.settings': '设置',
|
||||||
|
'input.estimated_tokens': '预估消耗',
|
||||||
|
'settings.temperature': '模型温度',
|
||||||
|
'settings.temperature.tip':
|
||||||
|
'模型生成文本的随机程度。值越大,回复内容越赋有多样性、创造性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7',
|
||||||
|
'settings.conext_count': '上下文数',
|
||||||
|
'settings.conext_count.tip':
|
||||||
|
'要保留在上下文中的消息数量,数值越大,上下文越长,消耗的 token 越多。普通聊天建议 5-10,代码生成建议 5-10',
|
||||||
|
'settings.reset': '重置',
|
||||||
|
'settings.set_as_default': '应用到默认助手',
|
||||||
|
'settings.max': '不限'
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: '智能体'
|
title: '智能体'
|
||||||
@@ -202,15 +236,16 @@ const resources = {
|
|||||||
ollama: 'Ollama',
|
ollama: 'Ollama',
|
||||||
baichuan: '百川',
|
baichuan: '百川',
|
||||||
dashscope: '阿里云灵积',
|
dashscope: '阿里云灵积',
|
||||||
anthropic: 'Anthropic'
|
anthropic: 'Anthropic',
|
||||||
|
aihubmix: 'AiHubMix'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: '设置',
|
title: '设置',
|
||||||
general: '常规',
|
general: '常规设置',
|
||||||
provider: '模型提供商',
|
provider: '模型提供商',
|
||||||
model: '模型设置',
|
model: '模型设置',
|
||||||
assistant: '默认助手',
|
assistant: '默认助手',
|
||||||
about: '关于',
|
about: '关于我们',
|
||||||
'general.title': '常规设置',
|
'general.title': '常规设置',
|
||||||
'provider.api_key': 'API 密钥',
|
'provider.api_key': 'API 密钥',
|
||||||
'provider.check': '检查',
|
'provider.check': '检查',
|
||||||
@@ -232,7 +267,8 @@ const resources = {
|
|||||||
'models.add.group_name.placeholder': '例如 ChatGPT',
|
'models.add.group_name.placeholder': '例如 ChatGPT',
|
||||||
'models.empty': '没有模型',
|
'models.empty': '没有模型',
|
||||||
'assistant.title': '默认助手',
|
'assistant.title': '默认助手',
|
||||||
'about.description': '一个为创造者而生的 AI 助手',
|
'assistant.model_params': '模型参数',
|
||||||
|
'about.description': '一款为创造者而生的 AI 助手',
|
||||||
'about.updateNotAvailable': '你的软件已是最新版本',
|
'about.updateNotAvailable': '你的软件已是最新版本',
|
||||||
'about.checkingUpdate': '正在检查更新...',
|
'about.checkingUpdate': '正在检查更新...',
|
||||||
'about.updateError': '更新出错',
|
'about.updateError': '更新出错',
|
||||||
@@ -241,7 +277,16 @@ const resources = {
|
|||||||
'provider.delete.title': '删除提供商',
|
'provider.delete.title': '删除提供商',
|
||||||
'provider.delete.content': '确定要删除此模型提供商吗?',
|
'provider.delete.content': '确定要删除此模型提供商吗?',
|
||||||
'provider.edit.name': '模型提供商名称',
|
'provider.edit.name': '模型提供商名称',
|
||||||
'provider.edit.name.placeholder': '例如 OpenAI'
|
'provider.edit.name.placeholder': '例如 OpenAI',
|
||||||
|
'about.title': '关于我们',
|
||||||
|
'about.releases.title': '📔 更新日志',
|
||||||
|
'about.releases.button': '查看',
|
||||||
|
'about.website.title': '🌐 官方网站',
|
||||||
|
'about.website.button': '查看',
|
||||||
|
'about.feedback.title': '📝 意见反馈',
|
||||||
|
'about.feedback.button': '反馈',
|
||||||
|
'about.contact.title': '📧 邮件联系',
|
||||||
|
'about.contact.button': '邮件'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
180
src/renderer/src/pages/home/components/AssistantSettings.tsx
Normal file
180
src/renderer/src/pages/home/components/AssistantSettings.tsx
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import { QuestionCircleOutlined } from '@ant-design/icons'
|
||||||
|
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||||
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
|
import { Assistant } from '@renderer/types'
|
||||||
|
import { Button, Col, InputNumber, Popover, Row, Slider, Tooltip } from 'antd'
|
||||||
|
import { debounce } from 'lodash'
|
||||||
|
import { FC, PropsWithChildren, useCallback, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
assistant: Assistant
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopoverContent: FC<Props> = (props) => {
|
||||||
|
const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id)
|
||||||
|
const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||||
|
const [contextCount, setConextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const onUpdateAssistantSettings = useCallback(
|
||||||
|
debounce(
|
||||||
|
({ _temperature, _contextCount }: { _temperature?: number; _contextCount?: number }) => {
|
||||||
|
updateAssistantSettings({
|
||||||
|
...assistant.settings,
|
||||||
|
temperature: _temperature ?? temperature,
|
||||||
|
contextCount: _contextCount ?? contextCount
|
||||||
|
})
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
{
|
||||||
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onTemperatureChange = (value) => {
|
||||||
|
if (!isNaN(value as number)) {
|
||||||
|
setTemperature(value)
|
||||||
|
onUpdateAssistantSettings({ _temperature: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConextCountChange = (value) => {
|
||||||
|
if (!isNaN(value as number)) {
|
||||||
|
setConextCount(value)
|
||||||
|
onUpdateAssistantSettings({ _contextCount: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
setTemperature(DEFAULT_TEMPERATURE)
|
||||||
|
setConextCount(DEFAULT_CONEXTCOUNT)
|
||||||
|
updateAssistant({
|
||||||
|
...assistant,
|
||||||
|
settings: {
|
||||||
|
...assistant.settings,
|
||||||
|
temperature: DEFAULT_TEMPERATURE,
|
||||||
|
contextCount: DEFAULT_CONEXTCOUNT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTemperature(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||||
|
setConextCount(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||||
|
}, [assistant])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Row align="middle" justify="end">
|
||||||
|
<Label>{t('assistant.settings.temperature')}</Label>
|
||||||
|
<Tooltip title={t('assistant.settings.temperature.tip')}>
|
||||||
|
<QuestionIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col span={14}>
|
||||||
|
<Slider
|
||||||
|
min={0}
|
||||||
|
max={1.2}
|
||||||
|
onChange={onTemperatureChange}
|
||||||
|
value={typeof temperature === 'number' ? temperature : 0}
|
||||||
|
marks={{ 0: '0', 0.7: '0.7', 1: '1', 1.2: '1.2' }}
|
||||||
|
step={0.1}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={3}>
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
max={1.2}
|
||||||
|
style={{ width: 50, marginLeft: 5, textAlign: 'center' }}
|
||||||
|
step={0.1}
|
||||||
|
value={temperature}
|
||||||
|
onChange={onTemperatureChange}
|
||||||
|
controls={false}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Row align="middle" justify="end">
|
||||||
|
<Label>{t('assistant.settings.conext_count')}</Label>
|
||||||
|
<Tooltip title={t('assistant.settings.conext_count.tip')}>
|
||||||
|
<QuestionIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col span={14}>
|
||||||
|
<Slider
|
||||||
|
min={0}
|
||||||
|
max={20}
|
||||||
|
marks={{ 0: '0', 5: '5', 10: '10', 15: '15', 20: t('assistant.settings.max') }}
|
||||||
|
onChange={onConextCountChange}
|
||||||
|
value={typeof contextCount === 'number' ? contextCount : 0}
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={3}>
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
max={20}
|
||||||
|
style={{ width: 50, marginLeft: 5, textAlign: 'center' }}
|
||||||
|
step={1}
|
||||||
|
value={contextCount}
|
||||||
|
onChange={onConextCountChange}
|
||||||
|
controls={false}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row justify="center">
|
||||||
|
<Button onClick={onReset}>{t('assistant.settings.reset')}</Button>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssistantSettings: FC<Props & PropsWithChildren> = ({ children, assistant }) => {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover content={<PopoverContent assistant={assistant} />} trigger="click" onOpenChange={setOpen}>
|
||||||
|
{open ? (
|
||||||
|
children
|
||||||
|
) : (
|
||||||
|
<Tooltip placement="top" title={t('assistant.input.settings')} arrow>
|
||||||
|
{children}
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 420px;
|
||||||
|
padding: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Label = styled.p`
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const QuestionIcon = styled(QuestionCircleOutlined)`
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
`
|
||||||
|
|
||||||
|
export default AssistantSettings
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
|
import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
|
||||||
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
|
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
|
||||||
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
|
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
|
||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||||
|
import { useAppSelector } from '@renderer/store'
|
||||||
import { Assistant } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
import { droppableReorder, uuid } from '@renderer/utils'
|
import { droppableReorder, uuid } from '@renderer/utils'
|
||||||
import { Dropdown, MenuProps } from 'antd'
|
import { Dropdown } from 'antd'
|
||||||
|
import { ItemType } from 'antd/es/menu/interface'
|
||||||
import { last } from 'lodash'
|
import { last } from 'lodash'
|
||||||
import { FC, useRef } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@@ -18,52 +20,48 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAssistant }) => {
|
const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAssistant }) => {
|
||||||
const { assistants, removeAssistant, updateAssistant, addAssistant, updateAssistants } = useAssistants()
|
const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
|
||||||
const targetAssistant = useRef<Assistant | null>(null)
|
const { updateAssistant } = useAssistant(activeAssistant.id)
|
||||||
|
const generating = useAppSelector((state) => state.runtime.generating)
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onDelete = (assistant: Assistant) => {
|
const onDelete = (assistant: Assistant) => {
|
||||||
|
const _assistant = last(assistants.filter((a) => a.id !== assistant.id))
|
||||||
|
_assistant ? setActiveAssistant(_assistant) : onCreateAssistant()
|
||||||
removeAssistant(assistant.id)
|
removeAssistant(assistant.id)
|
||||||
setTimeout(() => {
|
|
||||||
const _assistant = last(assistants.filter((a) => a.id !== assistant.id))
|
|
||||||
_assistant ? setActiveAssistant(_assistant) : onCreateAssistant()
|
|
||||||
}, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const items: MenuProps['items'] = [
|
const getMenuItems = (assistant: Assistant) =>
|
||||||
{
|
[
|
||||||
label: t('common.edit'),
|
{
|
||||||
key: 'edit',
|
label: t('common.edit'),
|
||||||
icon: <EditOutlined />,
|
key: 'edit',
|
||||||
async onClick() {
|
icon: <EditOutlined />,
|
||||||
if (targetAssistant.current) {
|
async onClick() {
|
||||||
const _assistant = await AssistantSettingPopup.show({ assistant: targetAssistant.current })
|
const _assistant = await AssistantSettingPopup.show({ assistant })
|
||||||
updateAssistant(_assistant)
|
updateAssistant(_assistant)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('common.duplicate'),
|
||||||
|
key: 'duplicate',
|
||||||
|
icon: <CopyOutlined />,
|
||||||
|
onClick: async () => {
|
||||||
|
const _assistant: Assistant = { ...assistant, id: uuid(), topics: [getDefaultTopic()] }
|
||||||
|
addAssistant(_assistant)
|
||||||
|
setActiveAssistant(_assistant)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: t('common.delete'),
|
||||||
|
key: 'delete',
|
||||||
|
icon: <DeleteOutlined />,
|
||||||
|
danger: true,
|
||||||
|
onClick: () => onDelete(assistant)
|
||||||
}
|
}
|
||||||
},
|
] as ItemType[]
|
||||||
{
|
|
||||||
label: t('common.duplicate'),
|
|
||||||
key: 'duplicate',
|
|
||||||
icon: <CopyOutlined />,
|
|
||||||
async onClick() {
|
|
||||||
const assistant: Assistant = { ...activeAssistant, id: uuid(), topics: [getDefaultTopic()] }
|
|
||||||
addAssistant(assistant)
|
|
||||||
setActiveAssistant(assistant)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
label: t('common.delete'),
|
|
||||||
key: 'delete',
|
|
||||||
icon: <DeleteOutlined />,
|
|
||||||
danger: true,
|
|
||||||
onClick: () => {
|
|
||||||
targetAssistant.current && onDelete(targetAssistant.current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
const onDragEnd = (result: DropResult) => {
|
||||||
if (result.destination) {
|
if (result.destination) {
|
||||||
@@ -74,6 +72,14 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSwitchAssistant = (assistant: Assistant) => {
|
||||||
|
if (generating) {
|
||||||
|
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setActiveAssistant(assistant)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
@@ -84,13 +90,9 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
|||||||
<Draggable key={`draggable_${assistant.id}_${index}`} draggableId={assistant.id} index={index}>
|
<Draggable key={`draggable_${assistant.id}_${index}`} draggableId={assistant.id} index={index}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
||||||
<Dropdown
|
<Dropdown key={assistant.id} menu={{ items: getMenuItems(assistant) }} trigger={['contextMenu']}>
|
||||||
key={assistant.id}
|
|
||||||
menu={{ items }}
|
|
||||||
trigger={['contextMenu']}
|
|
||||||
onOpenChange={() => (targetAssistant.current = assistant)}>
|
|
||||||
<AssistantItem
|
<AssistantItem
|
||||||
onClick={() => setActiveAssistant(assistant)}
|
onClick={() => onSwitchAssistant(assistant)}
|
||||||
className={assistant.id === activeAssistant?.id ? 'active' : ''}>
|
className={assistant.id === activeAssistant?.id ? 'active' : ''}>
|
||||||
<AssistantName>{assistant.name}</AssistantName>
|
<AssistantName>{assistant.name}</AssistantName>
|
||||||
</AssistantItem>
|
</AssistantItem>
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ const Chat: FC<Props> = (props) => {
|
|||||||
const { assistant } = useAssistant(props.assistant.id)
|
const { assistant } = useAssistant(props.assistant.id)
|
||||||
const { activeTopic, setActiveTopic } = useActiveTopic(assistant)
|
const { activeTopic, setActiveTopic } = useActiveTopic(assistant)
|
||||||
|
|
||||||
if (!assistant) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="chat">
|
<Container id="chat">
|
||||||
<Flex vertical flex={1} justify="space-between">
|
<Flex vertical flex={1} justify="space-between">
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
|
||||||
import { Assistant, Message, Topic } from '@renderer/types'
|
|
||||||
import { uuid } from '@renderer/utils'
|
|
||||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
|
||||||
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/useAssistant'
|
|
||||||
import {
|
import {
|
||||||
ClearOutlined,
|
ClearOutlined,
|
||||||
|
ControlOutlined,
|
||||||
FullscreenExitOutlined,
|
FullscreenExitOutlined,
|
||||||
FullscreenOutlined,
|
FullscreenOutlined,
|
||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
|
MoreOutlined,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
PlusCircleOutlined
|
PlusCircleOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { isEmpty } from 'lodash'
|
|
||||||
import SendMessageSetting from './SendMessageSetting'
|
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import dayjs from 'dayjs'
|
import { useShowRightSidebar } from '@renderer/hooks/useStore'
|
||||||
import store, { useAppSelector } from '@renderer/store'
|
|
||||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
|
import store, { useAppSelector } from '@renderer/store'
|
||||||
import { setGenerating } from '@renderer/store/runtime'
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
|
import { Assistant, Message, Topic } from '@renderer/types'
|
||||||
|
import { estimateInputTokenCount, uuid } from '@renderer/utils'
|
||||||
|
import { Button, Popconfirm, Tooltip } from 'antd'
|
||||||
|
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { debounce, isEmpty } from 'lodash'
|
||||||
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import AssistantSettings from './AssistantSettings'
|
||||||
|
import SendMessageSetting from './SendMessageSetting'
|
||||||
|
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@@ -36,9 +39,9 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
const { addTopic } = useAssistant(assistant.id)
|
const { addTopic } = useAssistant(assistant.id)
|
||||||
const { sendMessageShortcut } = useSettings()
|
const { sendMessageShortcut } = useSettings()
|
||||||
const [expended, setExpend] = useState(false)
|
const [expended, setExpend] = useState(false)
|
||||||
|
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
||||||
const generating = useAppSelector((state) => state.runtime.generating)
|
const generating = useAppSelector((state) => state.runtime.generating)
|
||||||
const inputRef = useRef<TextAreaRef>(null)
|
const inputRef = useRef<TextAreaRef>(null)
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
@@ -65,6 +68,8 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
setText('')
|
setText('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inputTokenCount = useMemo(() => estimateInputTokenCount(text), [text])
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (sendMessageShortcut === 'Enter' && event.key === 'Enter') {
|
if (sendMessageShortcut === 'Enter' && event.key === 'Enter') {
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
@@ -81,7 +86,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addNewTopic = useCallback(() => {
|
const addNewTopic = useCallback(() => {
|
||||||
const topic: Topic = getDefaultTopic()
|
const topic = getDefaultTopic()
|
||||||
addTopic(topic)
|
addTopic(topic)
|
||||||
setActiveTopic(topic)
|
setActiveTopic(topic)
|
||||||
}, [addTopic, setActiveTopic])
|
}, [addTopic, setActiveTopic])
|
||||||
@@ -108,11 +113,13 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
}, [addNewTopic, generating])
|
}, [addNewTopic, generating])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const _setEstimateTokenCount = debounce(setEstimateTokenCount, 100, { leading: false, trailing: true })
|
||||||
const unsubscribes = [
|
const unsubscribes = [
|
||||||
EventEmitter.on(EVENT_NAMES.EDIT_MESSAGE, (message: Message) => {
|
EventEmitter.on(EVENT_NAMES.EDIT_MESSAGE, (message: Message) => {
|
||||||
setText(message.content)
|
setText(message.content)
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
})
|
}),
|
||||||
|
EventEmitter.on(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, _setEstimateTokenCount)
|
||||||
]
|
]
|
||||||
return () => unsubscribes.forEach((unsub) => unsub())
|
return () => unsubscribes.forEach((unsub) => unsub())
|
||||||
}, [])
|
}, [])
|
||||||
@@ -148,6 +155,11 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<AssistantSettings assistant={assistant}>
|
||||||
|
<ToolbarButton type="text">
|
||||||
|
<ControlOutlined />
|
||||||
|
</ToolbarButton>
|
||||||
|
</AssistantSettings>
|
||||||
<Tooltip placement="top" title={expended ? t('assistant.input.collapse') : t('assistant.input.expand')} arrow>
|
<Tooltip placement="top" title={expended ? t('assistant.input.collapse') : t('assistant.input.expand')} arrow>
|
||||||
<ToolbarButton type="text" onClick={() => setExpend(!expended)}>
|
<ToolbarButton type="text" onClick={() => setExpend(!expended)}>
|
||||||
{expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
{expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
||||||
@@ -177,9 +189,14 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
autoFocus
|
autoFocus
|
||||||
contextMenu="true"
|
contextMenu="true"
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
styles={{ textarea: { paddingLeft: 0 } }}
|
showCount
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
styles={{ textarea: { paddingLeft: 0 } }}
|
||||||
/>
|
/>
|
||||||
|
<TextCount>
|
||||||
|
<HistoryOutlined /> {assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT} |{' '}
|
||||||
|
{t('assistant.input.estimated_tokens')}: {`${inputTokenCount}/${estimateTokenCount}`}
|
||||||
|
</TextCount>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -192,6 +209,7 @@ const Container = styled.div`
|
|||||||
border-top: 0.5px solid var(--color-border);
|
border-top: 0.5px solid var(--color-border);
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Textarea = styled(TextArea)`
|
const Textarea = styled(TextArea)`
|
||||||
@@ -235,4 +253,13 @@ const ToolbarButton = styled(Button)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const TextCount = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
bottom: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
z-index: 10;
|
||||||
|
`
|
||||||
|
|
||||||
export default Inputbar
|
export default Inputbar
|
||||||
|
|||||||
@@ -104,8 +104,8 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
|||||||
<MessageMetadata>{message.modelId}</MessageMetadata>
|
<MessageMetadata>{message.modelId}</MessageMetadata>
|
||||||
{message.usage && (
|
{message.usage && (
|
||||||
<>
|
<>
|
||||||
<MessageMetadata style={{ textTransform: 'uppercase' }}>
|
<MessageMetadata>
|
||||||
tokens used: {message.usage.total_tokens} (IN:{message.usage.prompt_tokens}/OUT:
|
tokens: {message.usage.total_tokens} (in:{message.usage.prompt_tokens}/out:
|
||||||
{message.usage.completion_tokens})
|
{message.usage.completion_tokens})
|
||||||
</MessageMetadata>
|
</MessageMetadata>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import MessageItem from './Message'
|
|||||||
import { reverse } from 'lodash'
|
import { reverse } from 'lodash'
|
||||||
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { estimateHistoryTokenCount, runAsyncFunction } from '@renderer/utils'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
import LocalStorage from '@renderer/services/storage'
|
||||||
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
@@ -22,7 +22,7 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
|||||||
const [lastMessage, setLastMessage] = useState<Message | null>(null)
|
const [lastMessage, setLastMessage] = useState<Message | null>(null)
|
||||||
const { updateTopic } = useAssistant(assistant.id)
|
const { updateTopic } = useAssistant(assistant.id)
|
||||||
const provider = useProviderByAssistant(assistant)
|
const provider = useProviderByAssistant(assistant)
|
||||||
const messagesRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const assistantDefaultMessage: Message = {
|
const assistantDefaultMessage: Message = {
|
||||||
id: 'assistant',
|
id: 'assistant',
|
||||||
@@ -65,7 +65,6 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribes = [
|
const unsubscribes = [
|
||||||
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => {
|
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => {
|
||||||
console.debug({ assistant, provider, message: msg, topic })
|
|
||||||
onSendMessage(msg)
|
onSendMessage(msg)
|
||||||
fetchChatCompletion({ assistant, messages: [...messages, msg], topic, onResponse: setLastMessage })
|
fetchChatCompletion({ assistant, messages: [...messages, msg], topic, onResponse: setLastMessage })
|
||||||
}),
|
}),
|
||||||
@@ -95,11 +94,15 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
|||||||
}, [topic.id])
|
}, [topic.id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
messagesRef.current?.scrollTo({ top: 100000, behavior: 'auto' })
|
containerRef.current?.scrollTo({ top: 100000, behavior: 'auto' })
|
||||||
}, [messages])
|
}, [messages])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
EventEmitter.emit(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, estimateHistoryTokenCount(assistant, messages))
|
||||||
|
}, [assistant, messages])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="messages" key={assistant.id} ref={messagesRef}>
|
<Container id="messages" key={assistant.id} ref={containerRef}>
|
||||||
{lastMessage && <MessageItem message={lastMessage} />}
|
{lastMessage && <MessageItem message={lastMessage} />}
|
||||||
{reverse([...messages]).map((message, index) => (
|
{reverse([...messages]).map((message, index) => (
|
||||||
<MessageItem key={message.id} message={message} showMenu index={index} onDeleteMessage={onDeleteMessage} />
|
<MessageItem key={message.id} message={message} showMenu index={index} onDeleteMessage={onDeleteMessage} />
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const SendMessageSetting: FC<Props> = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{ items: sendSettingItems, selectable: true, defaultSelectedKeys: [sendMessageShortcut] }}
|
menu={{ items: sendSettingItems, selectable: true, defaultSelectedKeys: [sendMessageShortcut] }}
|
||||||
placement="top"
|
placement="topRight"
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
arrow>
|
arrow>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import { useShowRightSidebar } from '@renderer/hooks/useStore'
|
|||||||
import { fetchMessagesSummary } from '@renderer/services/api'
|
import { fetchMessagesSummary } from '@renderer/services/api'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
import { Button, Dropdown, MenuProps, Popconfirm } from 'antd'
|
import { Button, Dropdown, MenuProps, Popconfirm } from 'antd'
|
||||||
import { FC, useRef } from 'react'
|
import { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/icons'
|
import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/icons'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
import LocalStorage from '@renderer/services/storage'
|
||||||
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
|
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
|
||||||
import { droppableReorder } from '@renderer/utils'
|
import { droppableReorder } from '@renderer/utils'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useAppSelector } from '@renderer/store'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@@ -18,60 +19,61 @@ interface Props {
|
|||||||
setActiveTopic: (topic: Topic) => void
|
setActiveTopic: (topic: Topic) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic }) => {
|
||||||
const { showRightSidebar } = useShowRightSidebar()
|
const { showRightSidebar } = useShowRightSidebar()
|
||||||
const { removeTopic, updateTopic, removeAllTopics, updateTopics } = useAssistant(assistant.id)
|
const { assistant, removeTopic, updateTopic, removeAllTopics, updateTopics } = useAssistant(_assistant.id)
|
||||||
const currentTopic = useRef<Topic | null>(null)
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const generating = useAppSelector((state) => state.runtime.generating)
|
||||||
|
|
||||||
const topicMenuItems: MenuProps['items'] = [
|
const getTopicMenuItems = (topic: Topic) => {
|
||||||
{
|
const menus: MenuProps['items'] = [
|
||||||
label: t('assistant.topics.auto_rename'),
|
{
|
||||||
key: 'auto-rename',
|
label: t('assistant.topics.auto_rename'),
|
||||||
icon: <SignatureOutlined />,
|
key: 'auto-rename',
|
||||||
async onClick() {
|
icon: <SignatureOutlined />,
|
||||||
if (currentTopic.current) {
|
async onClick() {
|
||||||
const messages = await LocalStorage.getTopicMessages(currentTopic.current.id)
|
const messages = await LocalStorage.getTopicMessages(topic.id)
|
||||||
if (messages.length >= 2) {
|
if (messages.length >= 2) {
|
||||||
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
||||||
if (summaryText) {
|
if (summaryText) {
|
||||||
updateTopic({ ...currentTopic.current, name: summaryText })
|
updateTopic({ ...topic, name: summaryText })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
{
|
label: t('common.rename'),
|
||||||
label: t('common.rename'),
|
key: 'rename',
|
||||||
key: 'rename',
|
icon: <EditOutlined />,
|
||||||
icon: <EditOutlined />,
|
async onClick() {
|
||||||
async onClick() {
|
const name = await PromptPopup.show({
|
||||||
const name = await PromptPopup.show({
|
title: t('assistant.topics.edit.title'),
|
||||||
title: t('assistant.topics.edit.title'),
|
message: t('assistant.topics.edit.placeholder'),
|
||||||
message: t('assistant.topics.edit.placeholder'),
|
defaultValue: topic?.name || ''
|
||||||
defaultValue: currentTopic.current?.name || ''
|
})
|
||||||
})
|
if (name && topic?.name !== name) {
|
||||||
if (name && currentTopic.current && currentTopic.current?.name !== name) {
|
updateTopic({ ...topic, name })
|
||||||
updateTopic({ ...currentTopic.current, name })
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
|
||||||
|
|
||||||
if (assistant.topics.length > 1) {
|
if (assistant.topics.length > 1) {
|
||||||
topicMenuItems.push({ type: 'divider' })
|
menus.push({ type: 'divider' })
|
||||||
topicMenuItems.push({
|
menus.push({
|
||||||
label: t('common.delete'),
|
label: t('common.delete'),
|
||||||
danger: true,
|
danger: true,
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
icon: <DeleteOutlined />,
|
icon: <DeleteOutlined />,
|
||||||
onClick() {
|
onClick() {
|
||||||
if (assistant.topics.length === 1) return
|
if (assistant.topics.length === 1) return
|
||||||
currentTopic.current && removeTopic(currentTopic.current)
|
removeTopic(topic)
|
||||||
currentTopic.current = null
|
setActiveTopic(assistant.topics[0])
|
||||||
setActiveTopic(assistant.topics[0])
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return menus
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
const onDragEnd = (result: DropResult) => {
|
||||||
@@ -82,12 +84,16 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showRightSidebar) {
|
const onSwitchTopic = (topic: Topic) => {
|
||||||
return null
|
if (generating) {
|
||||||
|
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setActiveTopic(topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={showRightSidebar ? '' : 'collapsed'}>
|
<Container style={{ display: showRightSidebar ? 'block' : 'none' }}>
|
||||||
<TopicTitle>
|
<TopicTitle>
|
||||||
<span>
|
<span>
|
||||||
{t('assistant.topics.title')} ({assistant.topics.length})
|
{t('assistant.topics.title')} ({assistant.topics.length})
|
||||||
@@ -112,14 +118,10 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
|||||||
<Draggable key={`draggable_${topic.id}_${index}`} draggableId={topic.id} index={index}>
|
<Draggable key={`draggable_${topic.id}_${index}`} draggableId={topic.id} index={index}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
||||||
<Dropdown
|
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
|
||||||
menu={{ items: topicMenuItems }}
|
|
||||||
trigger={['contextMenu']}
|
|
||||||
key={topic.id}
|
|
||||||
onOpenChange={(open) => open && (currentTopic.current = topic)}>
|
|
||||||
<TopicListItem
|
<TopicListItem
|
||||||
className={topic.id === activeTopic?.id ? 'active' : ''}
|
className={topic.id === activeTopic?.id ? 'active' : ''}
|
||||||
onClick={() => setActiveTopic(topic)}>
|
onClick={() => onSwitchTopic(topic)}>
|
||||||
{topic.name}
|
{topic.name}
|
||||||
</TopicListItem>
|
</TopicListItem>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Avatar, Button, Progress } from 'antd'
|
import { Avatar, Button, Progress, Row, Tag } from 'antd'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import Logo from '@renderer/assets/images/logo.png'
|
import Logo from '@renderer/assets/images/logo.png'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { runAsyncFunction } from '@renderer/utils'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Changelog from './components/Changelog'
|
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import { ProgressInfo } from 'electron-updater'
|
import { ProgressInfo } from 'electron-updater'
|
||||||
|
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from './components'
|
||||||
|
|
||||||
const AboutSettings: FC = () => {
|
const AboutSettings: FC = () => {
|
||||||
const [version, setVersion] = useState('')
|
const [version, setVersion] = useState('')
|
||||||
@@ -26,8 +26,17 @@ const AboutSettings: FC = () => {
|
|||||||
{ leading: true, trailing: false }
|
{ leading: true, trailing: false }
|
||||||
)
|
)
|
||||||
|
|
||||||
const onOpenWebsite = (suffix = '') => {
|
const onOpenWebsite = (url: string) => {
|
||||||
window.api.openWebsite('https://github.com/kangfenmao/cherry-studio' + suffix)
|
window.api.openWebsite(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mailto = async () => {
|
||||||
|
const email = 'kangfenmao@qq.com'
|
||||||
|
const subject = 'Cherry Studio Feedback'
|
||||||
|
const version = (await window.api.getAppInfo()).version
|
||||||
|
const platform = window.electron.process.platform
|
||||||
|
const url = `mailto:${email}?subject=${subject}&body=%0A%0AVersion: ${version} | Platform: ${platform}`
|
||||||
|
onOpenWebsite(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -69,57 +78,92 @@ const AboutSettings: FC = () => {
|
|||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<SettingContainer>
|
||||||
<AvatarWrapper onClick={() => onOpenWebsite()}>
|
<SettingTitle>{t('settings.about.title')}</SettingTitle>
|
||||||
{percent > 0 && (
|
<SettingDivider />
|
||||||
<ProgressCircle
|
<AboutHeader>
|
||||||
type="circle"
|
<Row align="middle">
|
||||||
size={104}
|
<AvatarWrapper onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio')}>
|
||||||
percent={percent}
|
{percent > 0 && (
|
||||||
showInfo={false}
|
<ProgressCircle
|
||||||
strokeLinecap="butt"
|
type="circle"
|
||||||
strokeColor="#67ad5b"
|
size={84}
|
||||||
/>
|
percent={percent}
|
||||||
)}
|
showInfo={false}
|
||||||
<Avatar src={Logo} size={100} style={{ marginTop: 50, minHeight: 100 }} />
|
strokeLinecap="butt"
|
||||||
</AvatarWrapper>
|
strokeColor="#67ad5b"
|
||||||
<Title>
|
/>
|
||||||
Cherry Studio <Version onClick={() => onOpenWebsite('/releases')}>(v{version})</Version>
|
)}
|
||||||
</Title>
|
<Avatar src={Logo} size={80} style={{ minHeight: 80 }} />
|
||||||
<Description>{t('settings.about.description')}</Description>
|
</AvatarWrapper>
|
||||||
<CheckUpdateButton onClick={onCheckUpdate} loading={checkUpdateLoading}>
|
<VersionWrapper>
|
||||||
{downloading ? t('settings.about.downloading') : t('settings.about.checkUpdate')}
|
<Title>Cherry Studio</Title>
|
||||||
</CheckUpdateButton>
|
<Description>{t('settings.about.description')}</Description>
|
||||||
<Changelog />
|
<Tag
|
||||||
</Container>
|
onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio/releases')}
|
||||||
|
color="cyan"
|
||||||
|
style={{ marginTop: 8, cursor: 'pointer' }}>
|
||||||
|
v{version}
|
||||||
|
</Tag>
|
||||||
|
</VersionWrapper>
|
||||||
|
</Row>
|
||||||
|
<CheckUpdateButton onClick={onCheckUpdate} loading={checkUpdateLoading}>
|
||||||
|
{downloading ? t('settings.about.downloading') : t('settings.about.checkUpdate')}
|
||||||
|
</CheckUpdateButton>
|
||||||
|
</AboutHeader>
|
||||||
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>{t('settings.about.releases.title')}</SettingRowTitle>
|
||||||
|
<Button onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio/releases')}>
|
||||||
|
{t('settings.about.releases.button')}
|
||||||
|
</Button>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>{t('settings.about.website.title')}</SettingRowTitle>
|
||||||
|
<Button onClick={() => onOpenWebsite('https://easys.run/cherry-studio')}>
|
||||||
|
{t('settings.about.website.button')}
|
||||||
|
</Button>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>{t('settings.about.feedback.title')}</SettingRowTitle>
|
||||||
|
<Button onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio/issues')}>
|
||||||
|
{t('settings.about.feedback.button')}
|
||||||
|
</Button>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>{t('settings.about.contact.title')}</SettingRowTitle>
|
||||||
|
<Button onClick={mailto}>{t('settings.about.contact.button')}</Button>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled.div`
|
const AboutHeader = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex-direction: row;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
height: calc(100vh - var(--navbar-height));
|
width: 100%;
|
||||||
overflow-y: scroll;
|
padding: 5px 0;
|
||||||
padding: 0;
|
`
|
||||||
padding-bottom: 50px;
|
|
||||||
|
const VersionWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 80px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Title = styled.div`
|
const Title = styled.div`
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
margin: 10px 0;
|
margin-bottom: 5px;
|
||||||
`
|
|
||||||
|
|
||||||
const Version = styled.span`
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-text-2);
|
|
||||||
margin: 10px 0;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const Description = styled.div`
|
const Description = styled.div`
|
||||||
@@ -128,18 +172,17 @@ const Description = styled.div`
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
`
|
`
|
||||||
|
|
||||||
const CheckUpdateButton = styled(Button)`
|
const CheckUpdateButton = styled(Button)``
|
||||||
margin-top: 10px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const AvatarWrapper = styled.div`
|
const AvatarWrapper = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
margin-right: 15px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProgressCircle = styled(Progress)`
|
const ProgressCircle = styled(Progress)`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 48px;
|
top: -2px;
|
||||||
left: -2px;
|
left: -2px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,66 @@
|
|||||||
import { FC } from 'react'
|
import { QuestionCircleOutlined } from '@ant-design/icons'
|
||||||
import { SettingContainer, SettingDivider, SettingSubtitle, SettingTitle } from './components'
|
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||||
import { Input } from 'antd'
|
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
|
||||||
import { useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
import { useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
||||||
|
import { Button, Col, Input, InputNumber, Row, Slider, Tooltip } from 'antd'
|
||||||
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
|
import { FC, useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { SettingContainer, SettingDivider, SettingSubtitle, SettingTitle } from './components'
|
||||||
|
import { debounce } from 'lodash'
|
||||||
|
|
||||||
const AssistantSettings: FC = () => {
|
const AssistantSettings: FC = () => {
|
||||||
const { defaultAssistant, updateDefaultAssistant } = useDefaultAssistant()
|
const { defaultAssistant, updateDefaultAssistant } = useDefaultAssistant()
|
||||||
|
const [temperature, setTemperature] = useState(defaultAssistant.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||||
|
const [contextCount, setConextCount] = useState(defaultAssistant.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const onUpdateAssistantSettings = useCallback(
|
||||||
|
debounce(
|
||||||
|
({ _temperature, _contextCount }: { _temperature?: number; _contextCount?: number }) => {
|
||||||
|
updateDefaultAssistant({
|
||||||
|
...defaultAssistant,
|
||||||
|
settings: {
|
||||||
|
...defaultAssistant.settings,
|
||||||
|
temperature: _temperature ?? temperature,
|
||||||
|
contextCount: _contextCount ?? contextCount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
{ leading: false, trailing: true }
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onTemperatureChange = (value) => {
|
||||||
|
if (!isNaN(value as number)) {
|
||||||
|
setTemperature(value)
|
||||||
|
onUpdateAssistantSettings({ _temperature: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConextCountChange = (value) => {
|
||||||
|
if (!isNaN(value as number)) {
|
||||||
|
setConextCount(value)
|
||||||
|
onUpdateAssistantSettings({ _contextCount: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
setTemperature(DEFAULT_TEMPERATURE)
|
||||||
|
setConextCount(DEFAULT_CONEXTCOUNT)
|
||||||
|
updateDefaultAssistant({
|
||||||
|
...defaultAssistant,
|
||||||
|
settings: {
|
||||||
|
...defaultAssistant.settings,
|
||||||
|
temperature: DEFAULT_TEMPERATURE,
|
||||||
|
contextCount: DEFAULT_CONEXTCOUNT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
<SettingTitle>{t('settings.assistant.title')}</SettingTitle>
|
<SettingTitle>{t('settings.assistant.title')}</SettingTitle>
|
||||||
@@ -27,8 +78,82 @@ const AssistantSettings: FC = () => {
|
|||||||
value={defaultAssistant.prompt}
|
value={defaultAssistant.prompt}
|
||||||
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, prompt: e.target.value })}
|
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, prompt: e.target.value })}
|
||||||
/>
|
/>
|
||||||
|
<SettingDivider />
|
||||||
|
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.assistant.model_params')}</SettingSubtitle>
|
||||||
|
<Row align="middle">
|
||||||
|
<Label>{t('assistant.settings.temperature')}</Label>
|
||||||
|
<Tooltip title={t('assistant.settings.temperature.tip')}>
|
||||||
|
<QuestionIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</Row>
|
||||||
|
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}>
|
||||||
|
<Col span={22}>
|
||||||
|
<Slider
|
||||||
|
min={0}
|
||||||
|
max={1.2}
|
||||||
|
onChange={onTemperatureChange}
|
||||||
|
value={typeof temperature === 'number' ? temperature : 0}
|
||||||
|
marks={{ 0: '0', 0.7: '0.7', 1: '1', 1.2: '1.2' }}
|
||||||
|
step={0.1}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
max={1.2}
|
||||||
|
step={0.1}
|
||||||
|
value={temperature}
|
||||||
|
onChange={onTemperatureChange}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row align="middle">
|
||||||
|
<Label>{t('assistant.settings.conext_count')}</Label>
|
||||||
|
<Tooltip title={t('assistant.settings.conext_count.tip')}>
|
||||||
|
<QuestionIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</Row>
|
||||||
|
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}>
|
||||||
|
<Col span={22}>
|
||||||
|
<Slider
|
||||||
|
min={0}
|
||||||
|
max={20}
|
||||||
|
marks={{ 0: '0', 5: '5', 10: '10', 15: '15', 20: t('assistant.settings.max') }}
|
||||||
|
onChange={onConextCountChange}
|
||||||
|
value={typeof contextCount === 'number' ? contextCount : 0}
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
max={20}
|
||||||
|
step={1}
|
||||||
|
value={contextCount}
|
||||||
|
onChange={onConextCountChange}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Button onClick={onReset} style={{ width: 100 }}>
|
||||||
|
{t('assistant.settings.reset')}
|
||||||
|
</Button>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Label = styled.p`
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const QuestionIcon = styled(QuestionCircleOutlined)`
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
`
|
||||||
|
|
||||||
export default AssistantSettings
|
export default AssistantSettings
|
||||||
|
|||||||
@@ -100,11 +100,11 @@ const ProviderSettings: FC = () => {
|
|||||||
key={JSON.stringify(provider)}
|
key={JSON.stringify(provider)}
|
||||||
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
||||||
onClick={() => setSelectedProvider(provider)}>
|
onClick={() => setSelectedProvider(provider)}>
|
||||||
{provider.isSystem && <Avatar src={getProviderLogo(provider.id)} size={24} />}
|
{provider.isSystem && <Avatar src={getProviderLogo(provider.id)} size={28} />}
|
||||||
{!provider.isSystem && (
|
{!provider.isSystem && (
|
||||||
<Avatar
|
<Avatar
|
||||||
size={24}
|
size={28}
|
||||||
style={{ backgroundColor: generateColorFromChar(provider.name), minWidth: 24 }}>
|
style={{ backgroundColor: generateColorFromChar(provider.name), minWidth: 28 }}>
|
||||||
{getFirstCharacter(provider.name)}
|
{getFirstCharacter(provider.name)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
)}
|
)}
|
||||||
@@ -151,7 +151,7 @@ const ProviderListContainer = styled.div`
|
|||||||
width: var(--assistants-width);
|
width: var(--assistants-width);
|
||||||
height: calc(100vh - var(--navbar-height));
|
height: calc(100vh - var(--navbar-height));
|
||||||
border-right: 0.5px solid var(--color-border);
|
border-right: 0.5px solid var(--color-border);
|
||||||
padding: 10px;
|
padding: 10px 8px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ const ProviderListItem = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 6px 10px;
|
padding: 5px 8px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import changelogEn from '@renderer/CHANGELOG.en.md?raw'
|
|
||||||
import changelogZh from '@renderer/CHANGELOG.zh.md?raw'
|
|
||||||
import { FC } from 'react'
|
|
||||||
import Markdown from 'react-markdown'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import styles from './changelog.module.scss'
|
|
||||||
import i18n from '@renderer/i18n'
|
|
||||||
|
|
||||||
const Changelog: FC = () => {
|
|
||||||
const language = i18n.language
|
|
||||||
const changelog = language === 'zh-CN' ? changelogZh : changelogEn
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<Markdown className={styles.markdown}>{changelog}</Markdown>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
margin-top: 40px;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 650px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default Changelog
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
$background-color: #121212;
|
|
||||||
$text-color: #ffffff;
|
|
||||||
$heading-color: #00b96b;
|
|
||||||
$link-color: #3498db;
|
|
||||||
$code-background: #1e1e1e;
|
|
||||||
$code-color: #f0e7db;
|
|
||||||
|
|
||||||
.markdown {
|
|
||||||
body {
|
|
||||||
background-color: $background-color;
|
|
||||||
color: $text-color;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
color: $heading-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 10px 0;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $link-color;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background-color: $code-background;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background-color: $code-background;
|
|
||||||
color: $code-color;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
border-left: 4px solid $heading-color;
|
|
||||||
padding-left: 10px;
|
|
||||||
margin-left: 0;
|
|
||||||
color: #b3b3b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
padding-left: 20px;
|
|
||||||
list-style: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import { ChatCompletionCreateParamsNonStreaming, ChatCompletionMessageParam } fr
|
|||||||
import { sum, takeRight } from 'lodash'
|
import { sum, takeRight } from 'lodash'
|
||||||
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources'
|
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources'
|
||||||
import { EVENT_NAMES } from './event'
|
import { EVENT_NAMES } from './event'
|
||||||
import { removeQuotes } from '@renderer/utils'
|
import { getAssistantSettings, removeQuotes } from '@renderer/utils'
|
||||||
|
|
||||||
export default class ProviderSDK {
|
export default class ProviderSDK {
|
||||||
provider: Provider
|
provider: Provider
|
||||||
@@ -32,10 +32,11 @@ export default class ProviderSDK {
|
|||||||
) {
|
) {
|
||||||
const defaultModel = getDefaultModel()
|
const defaultModel = getDefaultModel()
|
||||||
const model = assistant.model || defaultModel
|
const model = assistant.model || defaultModel
|
||||||
|
const { contextCount } = getAssistantSettings(assistant)
|
||||||
|
|
||||||
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
||||||
|
|
||||||
const userMessages = takeRight(messages, 5).map((message) => ({
|
const userMessages = takeRight(messages, contextCount + 1).map((message) => ({
|
||||||
role: message.role,
|
role: message.role,
|
||||||
content: message.content
|
content: message.content
|
||||||
}))
|
}))
|
||||||
@@ -43,9 +44,10 @@ export default class ProviderSDK {
|
|||||||
if (this.isAnthropic) {
|
if (this.isAnthropic) {
|
||||||
await this.anthropicSdk.messages
|
await this.anthropicSdk.messages
|
||||||
.stream({
|
.stream({
|
||||||
max_tokens: 2048,
|
model: model.id,
|
||||||
messages: [systemMessage, ...userMessages].filter(Boolean) as MessageParam[],
|
messages: [systemMessage, ...userMessages].filter(Boolean) as MessageParam[],
|
||||||
model: model.id
|
max_tokens: 4096,
|
||||||
|
temperature: assistant?.settings?.temperature
|
||||||
})
|
})
|
||||||
.on('text', (text) => onChunk({ text: text || '' }))
|
.on('text', (text) => onChunk({ text: text || '' }))
|
||||||
.on('finalMessage', (message) =>
|
.on('finalMessage', (message) =>
|
||||||
@@ -61,7 +63,8 @@ export default class ProviderSDK {
|
|||||||
const stream = await this.openaiSdk.chat.completions.create({
|
const stream = await this.openaiSdk.chat.completions.create({
|
||||||
model: model.id,
|
model: model.id,
|
||||||
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
|
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
|
||||||
stream: true
|
stream: true,
|
||||||
|
temperature: assistant?.settings?.temperature
|
||||||
})
|
})
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) break
|
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) break
|
||||||
@@ -80,7 +83,7 @@ export default class ProviderSDK {
|
|||||||
|
|
||||||
const systemMessage = {
|
const systemMessage = {
|
||||||
role: 'system',
|
role: 'system',
|
||||||
content: '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要加标点符号'
|
content: '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isAnthropic) {
|
if (this.isAnthropic) {
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ export const EVENT_NAMES = {
|
|||||||
ADD_ASSISTANT: 'ADD_ASSISTANT',
|
ADD_ASSISTANT: 'ADD_ASSISTANT',
|
||||||
EDIT_MESSAGE: 'EDIT_MESSAGE',
|
EDIT_MESSAGE: 'EDIT_MESSAGE',
|
||||||
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
|
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
|
||||||
CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED'
|
CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED',
|
||||||
|
ESTIMATED_TOKEN_COUNT: 'ESTIMATED_TOKEN_COUNT'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { getDefaultAssistant, getDefaultTopic } from '@renderer/services/assistant'
|
import { getDefaultAssistant, getDefaultTopic } from '@renderer/services/assistant'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
import LocalStorage from '@renderer/services/storage'
|
||||||
import { Assistant, Model, Topic } from '@renderer/types'
|
import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
|
||||||
import { uniqBy } from 'lodash'
|
import { uniqBy } from 'lodash'
|
||||||
|
|
||||||
export interface AssistantsState {
|
export interface AssistantsState {
|
||||||
@@ -33,6 +33,16 @@ const assistantsSlice = createSlice({
|
|||||||
updateAssistant: (state, action: PayloadAction<Assistant>) => {
|
updateAssistant: (state, action: PayloadAction<Assistant>) => {
|
||||||
state.assistants = state.assistants.map((c) => (c.id === action.payload.id ? action.payload : c))
|
state.assistants = state.assistants.map((c) => (c.id === action.payload.id ? action.payload : c))
|
||||||
},
|
},
|
||||||
|
updateAssistantSettings: (state, action: PayloadAction<{ assistantId: string; settings: AssistantSettings }>) => {
|
||||||
|
state.assistants = state.assistants.map((assistant) =>
|
||||||
|
assistant.id === action.payload.assistantId
|
||||||
|
? {
|
||||||
|
...assistant,
|
||||||
|
settings: action.payload.settings
|
||||||
|
}
|
||||||
|
: assistant
|
||||||
|
)
|
||||||
|
},
|
||||||
addTopic: (state, action: PayloadAction<{ assistantId: string; topic: Topic }>) => {
|
addTopic: (state, action: PayloadAction<{ assistantId: string; topic: Topic }>) => {
|
||||||
state.assistants = state.assistants.map((assistant) =>
|
state.assistants = state.assistants.map((assistant) =>
|
||||||
assistant.id === action.payload.assistantId
|
assistant.id === action.payload.assistantId
|
||||||
@@ -111,7 +121,8 @@ export const {
|
|||||||
updateTopic,
|
updateTopic,
|
||||||
updateTopics,
|
updateTopics,
|
||||||
removeAllTopics,
|
removeAllTopics,
|
||||||
setModel
|
setModel,
|
||||||
|
updateAssistantSettings
|
||||||
} = assistantsSlice.actions
|
} = assistantsSlice.actions
|
||||||
|
|
||||||
export default assistantsSlice.reducer
|
export default assistantsSlice.reducer
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 11,
|
version: 13,
|
||||||
blacklist: ['runtime'],
|
blacklist: ['runtime'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -94,6 +94,15 @@ const initialState: LlmState = {
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'aihubmix',
|
||||||
|
name: 'AiHubMix',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://aihubmix.com',
|
||||||
|
models: SYSTEM_MODELS.aihubmix.filter((m) => m.enabled),
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'openrouter',
|
id: 'openrouter',
|
||||||
name: 'OpenRouter',
|
name: 'OpenRouter',
|
||||||
|
|||||||
@@ -207,6 +207,42 @@ const migrate = createMigrate({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// @ts-ignore store type is unknown
|
||||||
|
'12': (state: RootState) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
llm: {
|
||||||
|
...state.llm,
|
||||||
|
providers: [
|
||||||
|
...state.llm.providers,
|
||||||
|
{
|
||||||
|
id: 'aihubmix',
|
||||||
|
name: 'AiHubMix',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://aihubmix.com',
|
||||||
|
models: SYSTEM_MODELS.aihubmix.filter((m) => m.enabled),
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// @ts-ignore store type is unknown
|
||||||
|
'13': (state: RootState) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
assistants: {
|
||||||
|
...state.assistants,
|
||||||
|
defaultAssistant: {
|
||||||
|
...state.assistants.defaultAssistant,
|
||||||
|
name: ['Default Assistant', '默认助手'].includes(state.assistants.defaultAssistant.name)
|
||||||
|
? i18n.t(`assistant.default.name`)
|
||||||
|
: state.assistants.defaultAssistant.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ export type Assistant = {
|
|||||||
prompt: string
|
prompt: string
|
||||||
topics: Topic[]
|
topics: Topic[]
|
||||||
model?: Model
|
model?: Model
|
||||||
|
settings?: AssistantSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AssistantSettings = {
|
||||||
|
contextCount: number
|
||||||
|
temperature: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import imageCompression from 'browser-image-compression'
|
import imageCompression from 'browser-image-compression'
|
||||||
import { Model } from '@renderer/types'
|
import { Assistant, AssistantSettings, Message, Model } from '@renderer/types'
|
||||||
|
import { GPTTokens } from 'gpt-tokens'
|
||||||
|
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||||
|
import { takeRight } from 'lodash'
|
||||||
|
|
||||||
export const runAsyncFunction = async (fn: () => void) => {
|
export const runAsyncFunction = async (fn: () => void) => {
|
||||||
await fn()
|
await fn()
|
||||||
@@ -164,3 +167,34 @@ export function getFirstCharacter(str) {
|
|||||||
return char
|
return char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAssistantSettings = (assistant: Assistant): AssistantSettings => {
|
||||||
|
const contextCount = assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT
|
||||||
|
return {
|
||||||
|
contextCount: contextCount === 20 ? 100000 : contextCount,
|
||||||
|
temperature: assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function estimateInputTokenCount(text: string) {
|
||||||
|
const input = new GPTTokens({
|
||||||
|
model: 'gpt-4o',
|
||||||
|
messages: [{ role: 'user', content: text }]
|
||||||
|
})
|
||||||
|
|
||||||
|
return input.usedTokens - 7
|
||||||
|
}
|
||||||
|
|
||||||
|
export function estimateHistoryTokenCount(assistant: Assistant, msgs: Message[]) {
|
||||||
|
const { contextCount } = getAssistantSettings(assistant)
|
||||||
|
|
||||||
|
const all = new GPTTokens({
|
||||||
|
model: 'gpt-4o',
|
||||||
|
messages: [
|
||||||
|
{ role: 'system', content: assistant.prompt },
|
||||||
|
...takeRight(msgs, contextCount).map((message) => ({ role: message.role, content: message.content }))
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
return all.usedTokens - 7
|
||||||
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
<p class="description">Windows/macOS GPT 客户端</p>
|
<p class="description">Windows/macOS GPT 客户端</p>
|
||||||
<div class="download-buttons">
|
<div class="download-buttons">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/kangfenmao/cherry-studio/releases/download/v0.2.8/Cherry-Studio-0.2.8-x64.dmg"
|
href="https://github.com/kangfenmao/cherry-studio/releases/download/v0.3.0/Cherry-Studio-0.3.0-x64.dmg"
|
||||||
class="download-btn">
|
class="download-btn">
|
||||||
<svg viewBox="0 0 384 512" width="24" height="24">
|
<svg viewBox="0 0 384 512" width="24" height="24">
|
||||||
<path
|
<path
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
macOS Intel
|
macOS Intel
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/kangfenmao/cherry-studio/releases/download/v0.2.8/Cherry-Studio-0.2.8-arm64.dmg"
|
href="https://github.com/kangfenmao/cherry-studio/releases/download/v0.3.0/Cherry-Studio-0.3.0-arm64.dmg"
|
||||||
class="download-btn">
|
class="download-btn">
|
||||||
<svg viewBox="0 0 384 512" width="24" height="24">
|
<svg viewBox="0 0 384 512" width="24" height="24">
|
||||||
<path
|
<path
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
macOS Apple Silicon
|
macOS Apple Silicon
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/kangfenmao/cherry-studio/releases/download/v0.2.8/Cherry-Studio-0.2.8-setup.exe"
|
href="https://github.com/kangfenmao/cherry-studio/releases/download/v0.3.0/Cherry-Studio-0.3.0-setup.exe"
|
||||||
class="download-btn">
|
class="download-btn">
|
||||||
<svg viewBox="0 0 448 512" width="24" height="24">
|
<svg viewBox="0 0 448 512" width="24" height="24">
|
||||||
<path
|
<path
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="new-app">
|
<p class="new-app">
|
||||||
🎉 <a href="https://github.com/kangfenmao/cherry-studio" target="_blank">Cherry Studio AI</a> 最新版本
|
🎉 <a href="https://github.com/kangfenmao/cherry-studio" target="_blank">Cherry Studio AI</a> 最新版本
|
||||||
<a href="https://github.com/kangfenmao/cherry-studio/releases/tag/v0.2.8" target="_blank">v0.2.8</a> 发布啦!
|
<a href="https://github.com/kangfenmao/cherry-studio/releases/tag/v0.3.0" target="_blank">v0.3.0</a> 发布啦!
|
||||||
</p>
|
</p>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<a href="https://github.com/kangfenmao/cherry-studio" target="_blank">开源</a> |
|
<a href="https://github.com/kangfenmao/cherry-studio" target="_blank">开源</a> |
|
||||||
|
|||||||
37
yarn.lock
37
yarn.lock
@@ -3440,6 +3440,7 @@ __metadata:
|
|||||||
eslint-plugin-react: "npm:^7.34.3"
|
eslint-plugin-react: "npm:^7.34.3"
|
||||||
eslint-plugin-react-hooks: "npm:^4.6.2"
|
eslint-plugin-react-hooks: "npm:^4.6.2"
|
||||||
eslint-plugin-unused-imports: "npm:^4.0.0"
|
eslint-plugin-unused-imports: "npm:^4.0.0"
|
||||||
|
gpt-tokens: "npm:^1.3.6"
|
||||||
i18next: "npm:^23.11.5"
|
i18next: "npm:^23.11.5"
|
||||||
localforage: "npm:^1.10.0"
|
localforage: "npm:^1.10.0"
|
||||||
lodash: "npm:^4.17.21"
|
lodash: "npm:^4.17.21"
|
||||||
@@ -3797,6 +3798,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"decimal.js@npm:^10.4.3":
|
||||||
|
version: 10.4.3
|
||||||
|
resolution: "decimal.js@npm:10.4.3"
|
||||||
|
checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"decode-named-character-reference@npm:^1.0.0":
|
"decode-named-character-reference@npm:^1.0.0":
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
resolution: "decode-named-character-reference@npm:1.0.2"
|
resolution: "decode-named-character-reference@npm:1.0.2"
|
||||||
@@ -5241,6 +5249,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"gpt-tokens@npm:^1.3.6":
|
||||||
|
version: 1.3.6
|
||||||
|
resolution: "gpt-tokens@npm:1.3.6"
|
||||||
|
dependencies:
|
||||||
|
decimal.js: "npm:^10.4.3"
|
||||||
|
js-tiktoken: "npm:^1.0.10"
|
||||||
|
openai-chat-tokens: "npm:^0.2.8"
|
||||||
|
checksum: 10c0/0efc1da655a16a306df4f17646832693d7cbec569fe44d4fcc9d4a605f8614f1eb974e04b24a4e8c71095fe0fab6de7251a34c6e2d6805a5e1b5811eea37437b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6":
|
"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6":
|
||||||
version: 4.2.11
|
version: 4.2.11
|
||||||
resolution: "graceful-fs@npm:4.2.11"
|
resolution: "graceful-fs@npm:4.2.11"
|
||||||
@@ -6067,6 +6086,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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"
|
||||||
|
dependencies:
|
||||||
|
base64-js: "npm:^1.5.1"
|
||||||
|
checksum: 10c0/7afb4826e21342386a1884754fbc1c1828f948c4dd0ab093bf778d1323e65343bd5343d15f7cda46af396f1fe4a0297739936149b7c40a0601eefe3fcaef8727
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
|
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "js-tokens@npm:4.0.0"
|
resolution: "js-tokens@npm:4.0.0"
|
||||||
@@ -7194,6 +7222,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"openai-chat-tokens@npm:^0.2.8":
|
||||||
|
version: 0.2.8
|
||||||
|
resolution: "openai-chat-tokens@npm:0.2.8"
|
||||||
|
dependencies:
|
||||||
|
js-tiktoken: "npm:^1.0.7"
|
||||||
|
checksum: 10c0/b415fda706b408f29b4584998990f29ad7f80f2ac1e84179a0976742ba8a80859fedeae5745a9bfe73443d95960b77328610074952ad198a18bc0e5c0ceb5b7b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"openai@npm:^4.52.1":
|
"openai@npm:^4.52.1":
|
||||||
version: 4.52.1
|
version: 4.52.1
|
||||||
resolution: "openai@npm:4.52.1"
|
resolution: "openai@npm:4.52.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user