feat: 智能体改进:名称、上下文支持、模型参数支持 #59
This commit is contained in:
parent
fe2e3bfc36
commit
43b9298329
6
.gitignore
vendored
6
.gitignore
vendored
@ -19,12 +19,6 @@ lerna-debug.log*
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# NPM
|
||||
npm/*/*
|
||||
!npm/*/dist
|
||||
!npm/*/package.json
|
||||
!npm/*/*.js
|
||||
|
||||
# Yarn
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
|
||||
1
npm/artifacts/README.md
Normal file
1
npm/artifacts/README.md
Normal file
@ -0,0 +1 @@
|
||||
# Cherry Studio Artifacts
|
||||
19
npm/artifacts/package.json
Normal file
19
npm/artifacts/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@cherry-studio/artifacts",
|
||||
"version": "0.1.0",
|
||||
"description": "Cherry Studio Artifacts",
|
||||
"main": "index.js",
|
||||
"homepage": "https://github.com/kangfenmao/cherry-studio/blob/main/npm/artifacts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"artifacts"
|
||||
],
|
||||
"author": "kangfenmao",
|
||||
"license": "ISC"
|
||||
}
|
||||
108
npm/artifacts/statics/word-explanation-card.css
Normal file
108
npm/artifacts/statics/word-explanation-card.css
Normal file
@ -0,0 +1,108 @@
|
||||
:root {
|
||||
/* 莫兰迪色系:使用柔和、低饱和度的颜色 */
|
||||
--primary-color: #b6b5a7; /* 莫兰迪灰褐色,用于背景文字 */
|
||||
--secondary-color: #9a8f8f; /* 莫兰迪灰棕色,用于标题背景 */
|
||||
--accent-color: #c5b4a0; /* 莫兰迪淡棕色,用于强调元素 */
|
||||
--background-color: #e8e3de; /* 莫兰迪米色,用于页面背景 */
|
||||
--text-color: #5b5b5b; /* 莫兰迪深灰色,用于主要文字 */
|
||||
--light-text-color: #8c8c8c; /* 莫兰迪中灰色,用于次要文字 */
|
||||
--divider-color: #d1cbc3; /* 莫兰迪浅灰色,用于分隔线 */
|
||||
}
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--background-color); /* 使用莫兰迪米色作为页面背景 */
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
color: var(--text-color); /* 使用莫兰迪深灰色作为主要文字颜色 */
|
||||
}
|
||||
.card {
|
||||
width: 300px;
|
||||
height: 500px;
|
||||
background-color: #f2ede9; /* 莫兰迪浅米色,用于卡片背景 */
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.header {
|
||||
background-color: var(--secondary-color); /* 使用莫兰迪灰棕色作为标题背景 */
|
||||
color: #f2ede9; /* 浅色文字与深色背景形成对比 */
|
||||
padding: 20px;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
h1 {
|
||||
font-family: 'Noto Serif SC', serif;
|
||||
font-size: 20px;
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
.content {
|
||||
padding: 30px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.word {
|
||||
text-align: left;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.word-main {
|
||||
font-family: 'Noto Serif SC', serif;
|
||||
font-size: 36px;
|
||||
color: var(--text-color); /* 使用莫兰迪深灰色作为主要词汇颜色 */
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.word-main::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -5px;
|
||||
width: 50px;
|
||||
height: 3px;
|
||||
background-color: var(--accent-color); /* 使用莫兰迪淡棕色作为下划线 */
|
||||
}
|
||||
.word-sub {
|
||||
font-size: 14px;
|
||||
color: var(--light-text-color); /* 使用莫兰迪中灰色作为次要文字颜色 */
|
||||
margin: 5px 0;
|
||||
}
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--divider-color); /* 使用莫兰迪浅灰色作为分隔线 */
|
||||
margin: 20px 0;
|
||||
}
|
||||
.explanation {
|
||||
font-size: 18px;
|
||||
line-height: 1.6;
|
||||
text-align: left;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.quote {
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
border-left: 3px solid var(--accent-color); /* 使用莫兰迪淡棕色作为引用边框 */
|
||||
}
|
||||
.background-text {
|
||||
position: absolute;
|
||||
font-size: 150px;
|
||||
color: rgba(182, 181, 167, 0.15); /* 使用莫兰迪灰褐色的透明版本作为背景文字 */
|
||||
z-index: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -8,7 +8,8 @@
|
||||
"homepage": "https://github.com/kangfenmao/cherry-studio",
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"local"
|
||||
"local",
|
||||
"npm/*"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@ -9,7 +9,6 @@ import Sidebar from './components/app/Sidebar'
|
||||
import TopViewContainer from './components/TopView'
|
||||
import AntdProvider from './context/AntdProvider'
|
||||
import { ThemeProvider } from './context/ThemeProvider'
|
||||
import AgentEditPage from './pages/agents/AgentEditPage'
|
||||
import AgentsPage from './pages/agents/AgentsPage'
|
||||
import AppsPage from './pages/apps/AppsPage'
|
||||
import FilesPage from './pages/files/FilesPage'
|
||||
@ -31,7 +30,6 @@ function App(): JSX.Element {
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/agents" element={<AgentsPage />} />
|
||||
<Route path="/agents/:id" element={<AgentEditPage />} />
|
||||
<Route path="/translate" element={<TranslatePage />} />
|
||||
<Route path="/apps" element={<AppsPage />} />
|
||||
<Route path="/messages/*" element={<HistoryPage />} />
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-scrollbar-thumb);
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background: var(--color-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
@ -0,0 +1,158 @@
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'
|
||||
import { Assistant, AssistantMessage, AssistantSettings } from '@renderer/types'
|
||||
import { Button, Card, Col, Divider, Form as FormAntd, FormInstance, Row, Space, Switch } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { FC, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
updateAssistant: (assistant: Assistant) => void
|
||||
updateAssistantSettings: (settings: Partial<AssistantSettings>) => void
|
||||
}
|
||||
|
||||
const AssistantMessagesSettings: FC<Props> = ({ assistant, updateAssistant, updateAssistantSettings }) => {
|
||||
const { t } = useTranslation()
|
||||
const [form] = Form.useForm()
|
||||
const formRef = useRef<FormInstance>(null)
|
||||
const [messages, setMessagess] = useState<AssistantMessage[]>(assistant?.messages || [])
|
||||
const [hideMessages, setHideMessages] = useState(assistant?.settings?.hideMessages || false)
|
||||
|
||||
const onSave = () => {
|
||||
// 检查是否有空对话组
|
||||
for (let i = 0; i < messages.length; i += 2) {
|
||||
const userContent = messages[i].content.trim()
|
||||
const assistantContent = messages[i + 1]?.content.trim()
|
||||
if (userContent === '' || assistantContent === '') {
|
||||
window.modal.error({
|
||||
centered: true,
|
||||
content: t('agents.edit.message.empty.content')
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤掉空消息并将消息分组
|
||||
const filteredMessagess = messages.reduce((acc, conv, index) => {
|
||||
if (index % 2 === 0) {
|
||||
const userContent = conv.content.trim()
|
||||
const assistantContent = messages[index + 1]?.content.trim()
|
||||
if (userContent !== '' || assistantContent !== '') {
|
||||
acc.push({ role: 'user', content: userContent }, { role: 'assistant', content: assistantContent })
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, [] as AssistantMessage[])
|
||||
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
messages: filteredMessagess
|
||||
})
|
||||
|
||||
window.message.success({ content: t('message.save.success.title'), key: 'save-messages' })
|
||||
}
|
||||
|
||||
const addMessages = () => {
|
||||
setMessagess([...messages, { role: 'user', content: '' }, { role: 'assistant', content: '' }])
|
||||
}
|
||||
|
||||
const updateMessages = (index: number, role: 'user' | 'assistant', content: string) => {
|
||||
const newMessagess = [...messages]
|
||||
newMessagess[index] = { role, content }
|
||||
setMessagess(newMessagess)
|
||||
}
|
||||
|
||||
const deleteMessages = (index: number) => {
|
||||
const newMessagess = [...messages]
|
||||
newMessagess.splice(index, 2) // 删除用户和助手的对话
|
||||
setMessagess(newMessagess)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Form ref={formRef} layout="vertical" form={form} labelAlign="right" colon={false}>
|
||||
<Form.Item label={t('agents.edit.settings.hide_preset_messages')}>
|
||||
<Switch
|
||||
checked={hideMessages}
|
||||
onChange={(checked) => {
|
||||
setHideMessages(checked)
|
||||
updateAssistantSettings({ hideMessages: checked })
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Divider style={{ marginBottom: 15 }} />
|
||||
<Form.Item label={t('agents.edit.message.group.title')}>
|
||||
{messages.map(
|
||||
(_, index) =>
|
||||
index % 2 === 0 && (
|
||||
<Card
|
||||
size="small"
|
||||
key={index}
|
||||
style={{ marginBottom: 16 }}
|
||||
title={`${t('agents.edit.message.group.title')} #${index / 2 + 1}`}
|
||||
extra={<Button icon={<DeleteOutlined />} type="text" danger onClick={() => deleteMessages(index)} />}>
|
||||
<Row gutter={16} align="middle" style={{ marginBottom: 16 }}>
|
||||
<Col span={3}>
|
||||
<label>{t('agents.edit.message.user.title')}</label>
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<TextArea
|
||||
value={messages[index].content}
|
||||
onChange={(e) => updateMessages(index, 'user', e.target.value)}
|
||||
placeholder={t('agents.edit.message.user.placeholder')}
|
||||
rows={1}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16} align="top">
|
||||
<Col span={3}>
|
||||
<label>{t('agents.edit.message.assistant.title')}</label>
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<TextArea
|
||||
value={messages[index + 1]?.content || ''}
|
||||
onChange={(e) => updateMessages(index + 1, 'assistant', e.target.value)}
|
||||
placeholder={t('agents.edit.message.assistant.placeholder')}
|
||||
rows={3}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
)}
|
||||
<Space>
|
||||
<Button icon={<PlusOutlined />} onClick={addMessages}>
|
||||
{t('agents.edit.message.add.title')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
<Divider style={{ marginBottom: 15 }} />
|
||||
<Form.Item>
|
||||
{messages.length > 0 && (
|
||||
<Button type="primary" onClick={onSave}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<div style={{ minHeight: 50 }} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding-top: 10px;
|
||||
`
|
||||
|
||||
const Form = styled(FormAntd)`
|
||||
.ant-form-item-no-colon {
|
||||
font-weight: 500;
|
||||
}
|
||||
`
|
||||
|
||||
export default AssistantMessagesSettings
|
||||
@ -1,81 +1,100 @@
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons'
|
||||
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { SettingRow, SettingRowTitle } from '@renderer/pages/settings'
|
||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import { SettingRow } from '@renderer/pages/settings'
|
||||
import { Assistant, AssistantSettings } from '@renderer/types'
|
||||
import { Button, Col, Row, Slider, Switch, Tooltip } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { Button, Col, Divider, Row, Slider, Switch, Tooltip } from 'antd'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import ModelAvatar from '../Avatar/ModelAvatar'
|
||||
import SelectModelPopup from '../Popups/SelectModelPopup'
|
||||
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
updateAssistant: (assistant: Assistant) => void
|
||||
updateAssistantSettings: (settings: Partial<AssistantSettings>) => void
|
||||
}
|
||||
|
||||
const AssistantModelSettings: FC<Props> = (props) => {
|
||||
const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id)
|
||||
const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateAssistantSettings }) => {
|
||||
const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||
const [contextCount, setConextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||
const [enableMaxTokens, setEnableMaxTokens] = useState(assistant?.settings?.enableMaxTokens ?? false)
|
||||
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
|
||||
const [autoResetModel, setAutoResetModel] = useState(assistant?.settings?.autoResetModel ?? false)
|
||||
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
||||
const [defaultModel, setDefaultModel] = useState(assistant?.defaultModel)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
||||
updateAssistantSettings({
|
||||
temperature: settings.temperature ?? temperature,
|
||||
contextCount: settings.contextCount ?? contextCount,
|
||||
enableMaxTokens: settings.enableMaxTokens ?? enableMaxTokens,
|
||||
maxTokens: settings.maxTokens ?? maxTokens,
|
||||
streamOutput: settings.streamOutput ?? streamOutput
|
||||
})
|
||||
}
|
||||
|
||||
const onTemperatureChange = (value) => {
|
||||
if (!isNaN(value as number)) {
|
||||
onUpdateAssistantSettings({ temperature: value })
|
||||
updateAssistantSettings({ temperature: value })
|
||||
}
|
||||
}
|
||||
|
||||
const onConextCountChange = (value) => {
|
||||
if (!isNaN(value as number)) {
|
||||
onUpdateAssistantSettings({ contextCount: value })
|
||||
updateAssistantSettings({ contextCount: value })
|
||||
}
|
||||
}
|
||||
|
||||
const onMaxTokensChange = (value) => {
|
||||
if (!isNaN(value as number)) {
|
||||
onUpdateAssistantSettings({ maxTokens: value })
|
||||
updateAssistantSettings({ maxTokens: value })
|
||||
}
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
setTemperature(DEFAULT_TEMPERATURE)
|
||||
setConextCount(DEFAULT_CONEXTCOUNT)
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
settings: {
|
||||
...assistant.settings,
|
||||
temperature: DEFAULT_TEMPERATURE,
|
||||
contextCount: DEFAULT_CONEXTCOUNT,
|
||||
enableMaxTokens: false,
|
||||
maxTokens: DEFAULT_MAX_TOKENS,
|
||||
streamOutput: true
|
||||
}
|
||||
})
|
||||
setEnableMaxTokens(false)
|
||||
setMaxTokens(0)
|
||||
setStreamOutput(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTemperature(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||
setConextCount(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||
setEnableMaxTokens(assistant?.settings?.enableMaxTokens ?? false)
|
||||
setMaxTokens(assistant?.settings?.maxTokens ?? DEFAULT_MAX_TOKENS)
|
||||
setStreamOutput(assistant?.settings?.streamOutput ?? true)
|
||||
}, [assistant])
|
||||
const onSelectModel = async () => {
|
||||
const selectedModel = await SelectModelPopup.show({ model: assistant?.model })
|
||||
if (selectedModel) {
|
||||
setDefaultModel(selectedModel)
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
defaultModel: selectedModel
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Row align="middle" style={{ marginBottom: 10 }}>
|
||||
<Label style={{ marginBottom: 10 }}>{t('assistants.settings.default_model')}</Label>
|
||||
<Col span={24}>
|
||||
<HStack alignItems="center">
|
||||
<Button
|
||||
icon={defaultModel ? <ModelAvatar model={defaultModel} size={20} /> : <PlusOutlined />}
|
||||
onClick={onSelectModel}>
|
||||
{defaultModel ? defaultModel.name : t('agents.edit.model.select.title')}
|
||||
</Button>
|
||||
</HStack>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider style={{ margin: '10px 0' }} />
|
||||
<SettingRow style={{ minHeight: 30 }}>
|
||||
<Label>
|
||||
{t('assistants.settings.auto_reset_model')}{' '}
|
||||
<Tooltip title={t('assistants.settings.auto_reset_model.tip')}>
|
||||
<QuestionIcon />
|
||||
</Tooltip>
|
||||
</Label>
|
||||
<Switch
|
||||
value={autoResetModel}
|
||||
onChange={(checked) => {
|
||||
setAutoResetModel(checked)
|
||||
updateAssistantSettings({ autoResetModel: checked })
|
||||
}}
|
||||
/>
|
||||
</SettingRow>
|
||||
<Divider style={{ margin: '10px 0' }} />
|
||||
<Row align="middle">
|
||||
<Label>{t('chat.settings.temperature')}</Label>
|
||||
<Tooltip title={t('chat.settings.temperature.tip')}>
|
||||
@ -95,10 +114,12 @@ const AssistantModelSettings: FC<Props> = (props) => {
|
||||
</Col>
|
||||
</Row>
|
||||
<Row align="middle">
|
||||
<Label>{t('chat.settings.conext_count')}</Label>
|
||||
<Tooltip title={t('chat.settings.conext_count.tip')}>
|
||||
<QuestionIcon />
|
||||
</Tooltip>
|
||||
<Label>
|
||||
{t('chat.settings.conext_count')}{' '}
|
||||
<Tooltip title={t('chat.settings.conext_count.tip')}>
|
||||
<QuestionIcon />
|
||||
</Tooltip>
|
||||
</Label>
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
@ -123,7 +144,7 @@ const AssistantModelSettings: FC<Props> = (props) => {
|
||||
checked={enableMaxTokens}
|
||||
onChange={(enabled) => {
|
||||
setEnableMaxTokens(enabled)
|
||||
onUpdateAssistantSettings({ enableMaxTokens: enabled })
|
||||
updateAssistantSettings({ enableMaxTokens: enabled })
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
@ -141,18 +162,17 @@ const AssistantModelSettings: FC<Props> = (props) => {
|
||||
</Col>
|
||||
</Row>
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('model.stream_output')}</SettingRowTitleSmall>
|
||||
<Label>{t('model.stream_output')}</Label>
|
||||
<Switch
|
||||
checked={streamOutput}
|
||||
onChange={(checked) => {
|
||||
setStreamOutput(checked)
|
||||
onUpdateAssistantSettings({ streamOutput: checked })
|
||||
updateAssistantSettings({ streamOutput: checked })
|
||||
}}
|
||||
/>
|
||||
</SettingRow>
|
||||
<HStack
|
||||
justifyContent="flex-end"
|
||||
style={{ marginTop: 20, padding: '10px 0', borderTop: '0.5px solid var(--color-border)' }}>
|
||||
<Divider style={{ margin: '15px 0' }} />
|
||||
<HStack justifyContent="flex-end">
|
||||
<Button onClick={onReset} style={{ width: 80 }} danger type="primary">
|
||||
{t('chat.settings.reset')}
|
||||
</Button>
|
||||
@ -170,8 +190,8 @@ const Container = styled.div`
|
||||
`
|
||||
|
||||
const Label = styled.p`
|
||||
margin: 0;
|
||||
margin-right: 5px;
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
const QuestionIcon = styled(QuestionCircleOutlined)`
|
||||
@ -180,8 +200,4 @@ const QuestionIcon = styled(QuestionCircleOutlined)`
|
||||
color: var(--color-text-3);
|
||||
`
|
||||
|
||||
const SettingRowTitleSmall = styled(SettingRowTitle)`
|
||||
font-size: 13px;
|
||||
`
|
||||
|
||||
export default AssistantModelSettings
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { syncAsistantToAgent } from '@renderer/services/assistant'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { Assistant, AssistantSettings } from '@renderer/types'
|
||||
import { Button, Input } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { Box, HStack, VStack } from '../Layout'
|
||||
import { Box, HStack } from '../Layout'
|
||||
|
||||
const AssistantPromptSettings: React.FC<{ assistant: Assistant; onOk: () => void }> = (props) => {
|
||||
const { assistant, updateAssistant } = useAssistant(props.assistant.id)
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
updateAssistant: (assistant: Assistant) => void
|
||||
updateAssistantSettings: (settings: AssistantSettings) => void
|
||||
onOk: () => void
|
||||
}
|
||||
|
||||
const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant, onOk }) => {
|
||||
const [name, setName] = useState(assistant.name)
|
||||
const [prompt, setPrompt] = useState(assistant.prompt)
|
||||
const { t } = useTranslation()
|
||||
@ -17,11 +22,10 @@ const AssistantPromptSettings: React.FC<{ assistant: Assistant; onOk: () => void
|
||||
const onUpdate = () => {
|
||||
const _assistant = { ...assistant, name, prompt }
|
||||
updateAssistant(_assistant)
|
||||
syncAsistantToAgent(_assistant)
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack flex={1}>
|
||||
<Container>
|
||||
<Box mb={8} style={{ fontWeight: 'bold' }}>
|
||||
{t('common.name')}
|
||||
</Box>
|
||||
@ -43,12 +47,20 @@ const AssistantPromptSettings: React.FC<{ assistant: Assistant; onOk: () => void
|
||||
style={{ minHeight: 'calc(80vh - 200px)', maxHeight: 'calc(80vh - 150px)' }}
|
||||
/>
|
||||
<HStack width="100%" justifyContent="flex-end" mt="10px">
|
||||
<Button type="primary" onClick={props.onOk}>
|
||||
<Button type="primary" onClick={onOk}>
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 5px;
|
||||
`
|
||||
|
||||
export default AssistantPromptSettings
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useAgent } from '@renderer/hooks/useAgents'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { Menu, Modal } from 'antd'
|
||||
import { useState } from 'react'
|
||||
@ -7,6 +8,7 @@ import styled from 'styled-components'
|
||||
|
||||
import { HStack } from '../Layout'
|
||||
import { TopView } from '../TopView'
|
||||
import AssistantMessagesSettings from './AssistantMessagesSettings'
|
||||
import AssistantModelSettings from './AssistantModelSettings'
|
||||
import AssistantPromptSettings from './AssistantPromptSettings'
|
||||
|
||||
@ -18,32 +20,43 @@ interface Props extends AssistantSettingPopupShowParams {
|
||||
resolve: (assistant: Assistant) => void
|
||||
}
|
||||
|
||||
const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve }) => {
|
||||
const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, ...props }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const { t } = useTranslation()
|
||||
const [menu, setMenu] = useState('prompt')
|
||||
const { theme } = useTheme()
|
||||
|
||||
const _useAssistant = useAssistant(props.assistant.id)
|
||||
const _useAgent = useAgent(props.assistant.id)
|
||||
const isAgent = props.assistant.type === 'agent'
|
||||
|
||||
const assistant = isAgent ? _useAgent.agent : _useAssistant.assistant
|
||||
const updateAssistant = isAgent ? _useAgent.updateAgent : _useAssistant.updateAssistant
|
||||
const updateAssistantSettings = isAgent ? _useAgent.updateAgentSettings : _useAssistant.updateAssistantSettings
|
||||
|
||||
const onOk = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
const afterClose = () => {
|
||||
resolve(assistant)
|
||||
}
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'prompt',
|
||||
label: t('assistants.prompt_settings')
|
||||
label: t('assistants.settings.prompt')
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
label: t('assistants.model_settings')
|
||||
label: t('assistants.settings.model')
|
||||
},
|
||||
{
|
||||
key: 'messages',
|
||||
label: t('assistants.settings.preset_messages')
|
||||
}
|
||||
]
|
||||
|
||||
@ -51,21 +64,19 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve })
|
||||
<StyledModal
|
||||
open={open}
|
||||
onOk={onOk}
|
||||
onCancel={handleCancel}
|
||||
afterClose={onClose}
|
||||
transitionName="ant-move-down"
|
||||
maskTransitionName="ant-fade"
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={afterClose}
|
||||
footer={null}
|
||||
title={assistant.name}
|
||||
transitionName="ant-move-down"
|
||||
styles={{
|
||||
content: {
|
||||
padding: 0,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid var(--color-border)',
|
||||
background: 'var(--color-background)'
|
||||
},
|
||||
header: { padding: '10px 15px', borderBottom: '0.5px solid var(--color-border)', margin: 0 },
|
||||
mask: { background: theme === 'light' ? 'rgba(255,255,255, 0.8)' : 'rgba(0,0,0, 0.8)' }
|
||||
header: { padding: '10px 15px', borderBottom: '0.5px solid var(--color-border)', margin: 0 }
|
||||
}}
|
||||
width="70vw"
|
||||
height="80vh"
|
||||
@ -81,8 +92,28 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve })
|
||||
/>
|
||||
</LeftMenu>
|
||||
<Settings>
|
||||
{menu === 'prompt' && <AssistantPromptSettings assistant={assistant} onOk={onOk} />}
|
||||
{menu === 'model' && <AssistantModelSettings assistant={assistant} />}
|
||||
{menu === 'prompt' && (
|
||||
<AssistantPromptSettings
|
||||
assistant={assistant}
|
||||
updateAssistant={updateAssistant}
|
||||
updateAssistantSettings={updateAssistantSettings}
|
||||
onOk={onOk}
|
||||
/>
|
||||
)}
|
||||
{menu === 'model' && (
|
||||
<AssistantModelSettings
|
||||
assistant={assistant}
|
||||
updateAssistant={updateAssistant}
|
||||
updateAssistantSettings={updateAssistantSettings}
|
||||
/>
|
||||
)}
|
||||
{menu === 'messages' && (
|
||||
<AssistantMessagesSettings
|
||||
assistant={assistant}
|
||||
updateAssistant={updateAssistant}
|
||||
updateAssistantSettings={updateAssistantSettings}
|
||||
/>
|
||||
)}
|
||||
</Settings>
|
||||
</HStack>
|
||||
</StyledModal>
|
||||
@ -111,7 +142,7 @@ const StyledModal = styled(Modal)`
|
||||
}
|
||||
.ant-menu-item {
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
color: var(--color-text-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -132,11 +163,7 @@ const StyledModal = styled(Modal)`
|
||||
}
|
||||
`
|
||||
|
||||
export default class AssistantSettingPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('AssistantSettingPopup')
|
||||
}
|
||||
export default class AssistantSettingsPopup {
|
||||
static show(props: AssistantSettingPopupShowParams) {
|
||||
return new Promise<Assistant>((resolve) => {
|
||||
TopView.show(
|
||||
@ -144,10 +171,10 @@ export default class AssistantSettingPopup {
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
TopView.hide('AssistantSettingsPopup')
|
||||
}}
|
||||
/>,
|
||||
'AssistantSettingPopup'
|
||||
'AssistantSettingsPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { TopView } from '@renderer/components/TopView'
|
||||
import systemAgents from '@renderer/config/agents.json'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { covertAgentToAssistant } from '@renderer/services/assistant'
|
||||
import { createAssistantFromAgent } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { Agent, Assistant } from '@renderer/types'
|
||||
import { Divider, Input, InputRef, Modal, Tag } from 'antd'
|
||||
@ -26,35 +26,22 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
const { assistants, addAssistant } = useAssistants()
|
||||
const inputRef = useRef<InputRef>(null)
|
||||
|
||||
const defaultAgent: Agent = useMemo(
|
||||
() => ({
|
||||
id: defaultAssistant.id,
|
||||
name: defaultAssistant.name,
|
||||
emoji: defaultAssistant.emoji || '',
|
||||
prompt: defaultAssistant.prompt,
|
||||
group: 'system'
|
||||
}),
|
||||
[defaultAssistant.emoji, defaultAssistant.id, defaultAssistant.name, defaultAssistant.prompt]
|
||||
)
|
||||
|
||||
const agents = useMemo(() => {
|
||||
const allAgents = [...userAgents, ...systemAgents] as Agent[]
|
||||
const list = [defaultAgent, ...allAgents.filter((agent) => !assistants.map((a) => a.id).includes(agent.id))]
|
||||
const list = [defaultAssistant, ...allAgents.filter((agent) => !assistants.map((a) => a.id).includes(agent.id))]
|
||||
return searchText
|
||||
? list.filter((agent) => agent.name.toLowerCase().includes(searchText.trim().toLocaleLowerCase()))
|
||||
: list
|
||||
}, [assistants, defaultAgent, searchText, userAgents])
|
||||
}, [assistants, defaultAssistant, searchText, userAgents])
|
||||
|
||||
const onCreateAssistant = (agent: Agent) => {
|
||||
if (agent.id !== 'default') {
|
||||
if (assistants.map((a) => a.id).includes(String(agent.id))) {
|
||||
return
|
||||
}
|
||||
const onCreateAssistant = async (agent: Agent) => {
|
||||
if (agent.id === 'default') {
|
||||
addAssistant(agent)
|
||||
return
|
||||
}
|
||||
|
||||
const assistant = covertAgentToAssistant(agent)
|
||||
const assistant = await createAssistantFromAgent(agent)
|
||||
|
||||
addAssistant(assistant)
|
||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
|
||||
resolve(assistant)
|
||||
setOpen(false)
|
||||
@ -112,8 +99,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
<HStack alignItems="center" gap={5}>
|
||||
{agent.emoji} {agent.name}
|
||||
</HStack>
|
||||
{agent.group === 'system' && <Tag color="green">{t('agents.tag.system')}</Tag>}
|
||||
{agent.group === 'user' && <Tag color="orange">{t('agents.tag.user')}</Tag>}
|
||||
{agent.id === 'default' && <Tag color="green">{t('agents.tag.system')}</Tag>}
|
||||
{agent.type === 'agent' && <Tag color="orange">{t('agents.tag.agent')}</Tag>}
|
||||
</AgentItem>
|
||||
))}
|
||||
</Container>
|
||||
|
||||
@ -80,7 +80,6 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
transitionName="ant-move-down"
|
||||
maskTransitionName="ant-fade"
|
||||
styles={{ content: { borderRadius: 20, padding: 0, overflow: 'hidden', paddingBottom: 20 } }}
|
||||
closeIcon={null}
|
||||
footer={null}>
|
||||
|
||||
48
src/renderer/src/config/prompts.ts
Normal file
48
src/renderer/src/config/prompts.ts
Normal file
@ -0,0 +1,48 @@
|
||||
export const AGENT_PROMPT = `
|
||||
你是一个 Prompt 生成器。你会将用户输入的信息整合成一个 Markdown 语法的结构化的 Prompt。请务必不要使用代码块输出,而是直接显示!
|
||||
|
||||
## Role :
|
||||
[请填写你想定义的角色名称]
|
||||
|
||||
## Background :
|
||||
[请描述角色的背景信息,例如其历史、来源或特定的知识背景]
|
||||
|
||||
## Preferences :
|
||||
[请描述角色的偏好或特定风格,例如对某种设计或文化的偏好]
|
||||
|
||||
## Profile :
|
||||
- version: 0.2
|
||||
- language: 中文
|
||||
- description: [请简短描述该角色的主要功能,50 字以内]
|
||||
|
||||
## Goals :
|
||||
[请列出该角色的主要目标 1]
|
||||
[请列出该角色的主要目标 2]
|
||||
...
|
||||
|
||||
## Constrains :
|
||||
[请列出该角色在互动中必须遵循的限制条件 1]
|
||||
[请列出该角色在互动中必须遵循的限制条件 2]
|
||||
...
|
||||
|
||||
## Skills :
|
||||
[为了在限制条件下实现目标,该角色需要拥有的技能 1]
|
||||
[为了在限制条件下实现目标,该角色需要拥有的技能 2]
|
||||
...
|
||||
|
||||
## Examples :
|
||||
[提供一个输出示例 1,展示角色的可能回答或行为]
|
||||
[提供一个输出示例 2]
|
||||
...
|
||||
|
||||
## OutputFormat :
|
||||
[请描述该角色的工作流程的第一步]
|
||||
[请描述该角色的工作流程的第二步]
|
||||
...
|
||||
|
||||
## Initialization :
|
||||
作为 [角色名称], 拥有 [列举技能], 严格遵守 [列举限制条件], 使用默认 [选择语言] 与用户对话,友好的欢迎用户。然后介绍自己,并提示用户输入.
|
||||
`
|
||||
|
||||
export const SUMMARIZE_PROMPT =
|
||||
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
|
||||
@ -1,28 +1,28 @@
|
||||
import { RootState } from '@renderer/store'
|
||||
import { addAgent, removeAgent, updateAgent, updateAgents } from '@renderer/store/agents'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { addAgent, removeAgent, updateAgent, updateAgents, updateAgentSettings } from '@renderer/store/agents'
|
||||
import { Agent, AssistantSettings } from '@renderer/types'
|
||||
|
||||
export function useAgents() {
|
||||
const agents = useSelector((state: RootState) => state.agents.agents)
|
||||
const dispatch = useDispatch()
|
||||
const agents = useAppSelector((state) => state.agents.agents)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
return {
|
||||
agents,
|
||||
updateAgents: (agents: Agent[]) => dispatch(updateAgents(agents)),
|
||||
addAgent: (agent: Agent) => dispatch(addAgent(agent)),
|
||||
removeAgent: (agent: Agent) => dispatch(removeAgent(agent)),
|
||||
updateAgent: (agent: Agent) => dispatch(updateAgent(agent)),
|
||||
updateAgents: (agents: Agent[]) => dispatch(updateAgents(agents))
|
||||
removeAgent: (id: string) => dispatch(removeAgent({ id }))
|
||||
}
|
||||
}
|
||||
|
||||
export function useAgent(id: string) {
|
||||
const agents = useSelector((state: RootState) => state.agents.agents)
|
||||
const dispatch = useDispatch()
|
||||
const agent = agents.find((a) => a.id === id)
|
||||
const agent = useAppSelector((state) => state.agents.agents.find((a) => a.id === id) as Agent)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
return {
|
||||
agent,
|
||||
updateAgent: (agent: Agent) => dispatch(updateAgent(agent))
|
||||
updateAgent: (agent: Agent) => dispatch(updateAgent(agent)),
|
||||
updateAgentSettings: (settings: Partial<AssistantSettings>) => {
|
||||
dispatch(updateAgentSettings({ assistantId: agent.id, settings }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ export function useAssistant(id: string) {
|
||||
removeAllTopics: () => dispatch(removeAllTopics({ assistantId: assistant.id })),
|
||||
setModel: (model: Model) => dispatch(setModel({ assistantId: assistant.id, model })),
|
||||
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
|
||||
updateAssistantSettings: (settings: AssistantSettings) => {
|
||||
updateAssistantSettings: (settings: Partial<AssistantSettings>) => {
|
||||
dispatch(updateAssistantSettings({ assistantId: assistant.id, settings }))
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +62,8 @@
|
||||
"upgrade.success.title": "Upgrade successfully",
|
||||
"upgrade.success.content": "Please restart the application to complete the upgrade",
|
||||
"upgrade.success.button": "Restart",
|
||||
"topic.added": "New topic added"
|
||||
"topic.added": "New topic added",
|
||||
"save.success.title": "Saved successfully"
|
||||
},
|
||||
"chat": {
|
||||
"save": "Save",
|
||||
@ -74,8 +75,6 @@
|
||||
"topics.edit.title": "Edit Name",
|
||||
"topics.edit.placeholder": "Enter new name",
|
||||
"topics.clear.title": "Clear Messages",
|
||||
"topics.delete.all.title": "Delete all topics",
|
||||
"topics.delete.all.content": "Are you sure you want to delete all topics?",
|
||||
"topics.move_to": "Move to",
|
||||
"topics.list": "Topic List",
|
||||
"topics.export.title": "Export",
|
||||
@ -118,8 +117,19 @@
|
||||
"title": "Assistants",
|
||||
"abbr": "Assistant",
|
||||
"search": "Search assistants...",
|
||||
"prompt_settings": "Prompt Settings",
|
||||
"model_settings": "Model Settings"
|
||||
"settings.prompt": "Prompt Settings",
|
||||
"settings.model": "Model Settings",
|
||||
"settings.preset_messages": "Preset Messages",
|
||||
"settings.default_model": "Default Model",
|
||||
"settings.auto_reset_model": "Auto Reset Model",
|
||||
"settings.auto_reset_model.tip": "Automatically reset the model when a new topic is created.",
|
||||
"edit.title": "Edit Assistant",
|
||||
"copy.title": "Copy Assistant",
|
||||
"clear.title": "Clear topics",
|
||||
"clear.content": "Clearing the topic will delete all topics and files in the assistant. Are you sure you want to continue?",
|
||||
"saveto.title": "Save to agent",
|
||||
"delete.title": "Delete Assistant",
|
||||
"delete.content": "Deleting an assistant will delete all topics and files under the assistant. Are you sure you want to delete it?"
|
||||
},
|
||||
"model": {
|
||||
"stream_output": "Stream Output",
|
||||
@ -136,18 +146,28 @@
|
||||
"agents": {
|
||||
"title": "Agents",
|
||||
"my_agents": "My Agents",
|
||||
"add.title": "Add Agent",
|
||||
"add.title": "Create Agent",
|
||||
"edit.title": "Edit Agent",
|
||||
"add.name": "Name",
|
||||
"add.name.placeholder": "Enter name",
|
||||
"add.prompt": "Prompt",
|
||||
"add.prompt.placeholder": "Enter prompt",
|
||||
"add.button": "Add",
|
||||
"add.button": "Add to Assistant",
|
||||
"manage.title": "Manage Agents",
|
||||
"delete.popup.content": "Are you sure you want to delete this agent?",
|
||||
"tag.default": "Default",
|
||||
"tag.system": "System",
|
||||
"tag.user": "Mine"
|
||||
"tag.agent": "Agent",
|
||||
"edit.message.title": "Preset messages",
|
||||
"edit.message.add.title": "Add",
|
||||
"edit.message.group.title": "Message Group",
|
||||
"edit.message.assistant.title": "Assistant",
|
||||
"edit.message.assistant.placeholder": "Enter assistant message",
|
||||
"edit.message.user.title": "User",
|
||||
"edit.message.user.placeholder": "Enter user message",
|
||||
"edit.message.empty.content": "Conversation input content cannot be empty",
|
||||
"edit.model.select.title": "Select Model",
|
||||
"edit.settings.hide_preset_messages": "Hide Preset Message"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "MinApp"
|
||||
@ -211,6 +231,9 @@
|
||||
"general.backup.button": "Backup",
|
||||
"general.restore.button": "Restore",
|
||||
"general.view_webdav_settings": "View WebDAV settings",
|
||||
"general.reset.title": "Data Reset",
|
||||
"general.reset.button": "Reset",
|
||||
"general.manually_check_update.title": "Manually Check for updates",
|
||||
"data.webdav.title": "WebDAV",
|
||||
"data.webdav.host": "WebDAV Host",
|
||||
"data.webdav.host.placeholder": "http://localhost:8080",
|
||||
@ -220,11 +243,6 @@
|
||||
"data.webdav.path.placeholder": "/backup",
|
||||
"data.webdav.backup.button": "Backup to WebDAV",
|
||||
"data.webdav.restore.button": "Restore from WebDAV",
|
||||
"general.reset.title": "Data Reset",
|
||||
"general.reset.button": "Reset",
|
||||
"general.check_update_setting": "Check for updates",
|
||||
"general.manual_update_check": "Check for updates manually",
|
||||
"general.auto_update_check": "Check for updates automatically",
|
||||
"advanced.title": "Advanced Settings",
|
||||
"advanced.click_assistant_switch_to_topics": "Auto switch to topic",
|
||||
"provider.api_key": "API Key",
|
||||
@ -285,7 +303,8 @@
|
||||
"font_size.title": "Message Font Size",
|
||||
"topic.position": "Topic Position",
|
||||
"topic.position.left": "Left",
|
||||
"topic.position.right": "Right"
|
||||
"topic.position.right": "Right",
|
||||
"topic.show.time": "Show Topic Time"
|
||||
},
|
||||
"translate": {
|
||||
"title": "Translation",
|
||||
|
||||
@ -62,7 +62,8 @@
|
||||
"upgrade.success.title": "升级成功",
|
||||
"upgrade.success.content": "重启应用以完成升级",
|
||||
"upgrade.success.button": "重启",
|
||||
"topic.added": "话题添加成功"
|
||||
"topic.added": "话题添加成功",
|
||||
"save.success.title": "保存成功"
|
||||
},
|
||||
"chat": {
|
||||
"save": "保存",
|
||||
@ -74,8 +75,6 @@
|
||||
"topics.edit.title": "编辑话题名",
|
||||
"topics.edit.placeholder": "输入新名称",
|
||||
"topics.clear.title": "清空消息",
|
||||
"topics.delete.all.title": "删除所有话题",
|
||||
"topics.delete.all.content": "确定要删除所有话题吗?",
|
||||
"topics.move_to": "移动到",
|
||||
"topics.list": "话题列表",
|
||||
"topics.export.title": "导出",
|
||||
@ -118,8 +117,19 @@
|
||||
"title": "助手",
|
||||
"abbr": "助手",
|
||||
"search": "搜索助手",
|
||||
"prompt_settings": "提示词设置",
|
||||
"model_settings": "模型设置"
|
||||
"settings.prompt": "提示词设置",
|
||||
"settings.model": "模型设置",
|
||||
"settings.preset_messages": "预设消息",
|
||||
"settings.default_model": "默认模型",
|
||||
"settings.auto_reset_model": "自动重置模型",
|
||||
"settings.auto_reset_model.tip": "创建新话题时自动重置模型",
|
||||
"edit.title": "编辑助手",
|
||||
"copy.title": "复制助手",
|
||||
"clear.title": "清空话题",
|
||||
"clear.content": "清空话题会删除助手下所有话题和文件,确定要继续吗?",
|
||||
"saveto.title": "保存到智能体",
|
||||
"delete.title": "删除助手",
|
||||
"delete.content": "删除助手会删除所有该助手下的话题和文件,确定要继续吗?"
|
||||
},
|
||||
"model": {
|
||||
"stream_output": "流式输出",
|
||||
@ -136,18 +146,28 @@
|
||||
"agents": {
|
||||
"title": "智能体",
|
||||
"my_agents": "我的智能体",
|
||||
"add.title": "添加智能体",
|
||||
"add.title": "创建智能体",
|
||||
"edit.title": "编辑智能体",
|
||||
"add.name": "名称",
|
||||
"add.name.placeholder": "输入名称",
|
||||
"add.prompt": "提示词",
|
||||
"add.prompt.placeholder": "输入提示词",
|
||||
"add.button": "添加",
|
||||
"add.button": "添加到助手",
|
||||
"manage.title": "管理智能体",
|
||||
"delete.popup.content": "确定要删除此智能体吗?",
|
||||
"tag.default": "默认",
|
||||
"tag.system": "系统",
|
||||
"tag.user": "我的"
|
||||
"tag.agent": "智能体",
|
||||
"edit.message.title": "预设消息",
|
||||
"edit.message.add.title": "添加",
|
||||
"edit.message.group.title": "消息组",
|
||||
"edit.message.assistant.title": "助手",
|
||||
"edit.message.assistant.placeholder": "输入助手消息",
|
||||
"edit.message.user.title": "用户",
|
||||
"edit.message.user.placeholder": "输入用户消息",
|
||||
"edit.message.empty.content": "会话输入内容不能为空",
|
||||
"edit.model.select.title": "选择模型",
|
||||
"edit.settings.hide_preset_messages": "隐藏预设消息"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "小程序"
|
||||
@ -213,6 +233,7 @@
|
||||
"general.reset.title": "重置数据",
|
||||
"general.reset.button": "重置",
|
||||
"general.view_webdav_settings": "查看 WebDAV 设置",
|
||||
"general.manually_check_update.title": "手动检查更新",
|
||||
"data.webdav.title": "WebDAV",
|
||||
"data.webdav.host": "WebDAV 地址",
|
||||
"data.webdav.host.placeholder": "http://localhost:8080",
|
||||
@ -222,9 +243,6 @@
|
||||
"data.webdav.path.placeholder": "/backup",
|
||||
"data.webdav.backup.button": "备份到 WebDAV",
|
||||
"data.webdav.restore.button": "从 WebDAV 恢复",
|
||||
"general.check_update_setting": "更新设置",
|
||||
"general.manual_update_check": "手动检查更新",
|
||||
"general.auto_update_check": "自动检查更新",
|
||||
"advanced.title": "高级设置",
|
||||
"advanced.click_assistant_switch_to_topics": "点击助手切换到话题",
|
||||
"provider.api_key": "API 密钥",
|
||||
@ -285,7 +303,8 @@
|
||||
"font_size.title": "消息字体大小",
|
||||
"topic.position": "话题位置",
|
||||
"topic.position.left": "左侧",
|
||||
"topic.position.right": "右侧"
|
||||
"topic.position.right": "右侧",
|
||||
"topic.show.time": "显示话题时间"
|
||||
},
|
||||
"translate": {
|
||||
"title": "翻译",
|
||||
|
||||
@ -62,7 +62,8 @@
|
||||
"upgrade.success.title": "升級成功",
|
||||
"upgrade.success.content": "請重新啟動應用以完成升級",
|
||||
"upgrade.success.button": "重新啟動",
|
||||
"topic.added": "新話題已添加"
|
||||
"topic.added": "新話題已添加",
|
||||
"save.success.title": "保存成功"
|
||||
},
|
||||
"chat": {
|
||||
"save": "保存",
|
||||
@ -74,8 +75,6 @@
|
||||
"topics.edit.title": "編輯名稱",
|
||||
"topics.edit.placeholder": "輸入新名稱",
|
||||
"topics.clear.title": "清空消息",
|
||||
"topics.delete.all.title": "刪除所有話題",
|
||||
"topics.delete.all.content": "確定要刪除所有話題嗎?",
|
||||
"topics.move_to": "移動到",
|
||||
"topics.list": "話題列表",
|
||||
"topics.export.title": "匯出",
|
||||
@ -118,8 +117,19 @@
|
||||
"title": "助手",
|
||||
"abbr": "助",
|
||||
"search": "搜尋助手...",
|
||||
"prompt_settings": "提示詞設定",
|
||||
"model_settings": "模型設定"
|
||||
"settings.prompt": "提示詞設定",
|
||||
"settings.model": "模型設定",
|
||||
"settings.preset_messages": "預設訊息",
|
||||
"settings.default_model": "預設模型",
|
||||
"settings.auto_reset_model": "自動重置模型",
|
||||
"settings.auto_reset_model.tip": "每次新的話題時自動重置模型",
|
||||
"edit.title": "編輯助手",
|
||||
"copy.title": "複製助手",
|
||||
"clear.title": "清空話題",
|
||||
"clear.content": "清空話題會刪除助手下所有主題和文件,確定要繼續嗎?",
|
||||
"saveto.title": "儲存到智能體",
|
||||
"delete.title": "删除助手",
|
||||
"delete.content": "删除助手会删除所有该助手下的话题和文件,确定要繼續吗?"
|
||||
},
|
||||
"model": {
|
||||
"stream_output": "串流輸出",
|
||||
@ -136,18 +146,28 @@
|
||||
"agents": {
|
||||
"title": "智能體",
|
||||
"my_agents": "我的智能體",
|
||||
"add.title": "添加智能體",
|
||||
"add.title": "创建智能體",
|
||||
"edit.title": "編輯智能體",
|
||||
"add.name": "名稱",
|
||||
"add.name.placeholder": "輸入名稱",
|
||||
"add.prompt": "提示詞",
|
||||
"add.prompt.placeholder": "輸入提示詞",
|
||||
"add.button": "添加",
|
||||
"add.button": "添加到助手",
|
||||
"manage.title": "管理智能體",
|
||||
"delete.popup.content": "確定要刪除此智能體嗎?",
|
||||
"tag.default": "預設",
|
||||
"tag.system": "系統",
|
||||
"tag.user": "我的"
|
||||
"tag.agent": "智能体",
|
||||
"edit.message.title": "預設訊息",
|
||||
"edit.message.add.title": "添加",
|
||||
"edit.message.group.title": "訊息組",
|
||||
"edit.message.assistant.title": "助手",
|
||||
"edit.message.assistant.placeholder": "輸入助手消息",
|
||||
"edit.message.user.title": "用戶",
|
||||
"edit.message.user.placeholder": "輸入用戶消息",
|
||||
"edit.message.empty.content": "會話輸入內容不能為空",
|
||||
"edit.model.select.title": "選擇模型",
|
||||
"edit.settings.hide_preset_messages": "隱藏預設消息"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "小程序"
|
||||
@ -211,6 +231,9 @@
|
||||
"general.backup.button": "備份",
|
||||
"general.restore.button": "復原",
|
||||
"general.view_webdav_settings": "查看 WebDAV 設定",
|
||||
"general.reset.title": "資料重置",
|
||||
"general.reset.button": "重置",
|
||||
"general.manually_check_update.title": "手動檢查更新",
|
||||
"data.webdav.title": "WebDAV",
|
||||
"data.webdav.host": "WebDAV 主機位址",
|
||||
"data.webdav.host.placeholder": "http://localhost:8080",
|
||||
@ -220,11 +243,6 @@
|
||||
"data.webdav.path.placeholder": "/backup",
|
||||
"data.webdav.backup.button": "從 WebDAV 備份",
|
||||
"data.webdav.restore.button": "從 WebDAV 恢復",
|
||||
"general.reset.title": "資料重置",
|
||||
"general.reset.button": "重置",
|
||||
"general.check_update_setting": "更新設定",
|
||||
"general.manual_update_check": "手動檢查更新",
|
||||
"general.auto_update_check": "自動檢查更新",
|
||||
"advanced.title": "進階設定",
|
||||
"advanced.click_assistant_switch_to_topics": "點擊助手切換到話題",
|
||||
"provider.api_key": "API 密鑰",
|
||||
@ -285,7 +303,8 @@
|
||||
"font_size.title": "訊息字體大小",
|
||||
"topic.position": "話題位置",
|
||||
"topic.position.left": "左側",
|
||||
"topic.position.right": "右側"
|
||||
"topic.position.right": "右側",
|
||||
"topic.show.time": "顯示話題時間"
|
||||
},
|
||||
"translate": {
|
||||
"title": "翻譯",
|
||||
|
||||
@ -1,161 +0,0 @@
|
||||
import { LoadingOutlined, ThunderboltOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import EmojiPicker from '@renderer/components/EmojiPicker'
|
||||
import { useAgent, useAgents } from '@renderer/hooks/useAgents'
|
||||
import { fetchGenerate } from '@renderer/services/api'
|
||||
import { syncAgentToAssistant } from '@renderer/services/assistant'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { getLeadingEmoji } from '@renderer/utils'
|
||||
import { Button, Form, FormInstance, Input, Popover } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate, useParams } from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
|
||||
type FieldType = {
|
||||
id: string
|
||||
name: string
|
||||
prompt: string
|
||||
}
|
||||
|
||||
const AgentEditPage: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams()
|
||||
const { agent } = useAgent(id!)
|
||||
const [form] = Form.useForm()
|
||||
const formRef = useRef<FormInstance>(null)
|
||||
const { addAgent, updateAgent } = useAgents()
|
||||
const [emoji, setEmoji] = useState(agent?.emoji)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onFinish = (values: FieldType) => {
|
||||
const _emoji = emoji || getLeadingEmoji(values.name)
|
||||
|
||||
if (values.name.trim() === '' || values.prompt.trim() === '') {
|
||||
return
|
||||
}
|
||||
|
||||
const _agent = {
|
||||
...agent,
|
||||
name: values.name,
|
||||
emoji: _emoji,
|
||||
prompt: values.prompt
|
||||
} as Agent
|
||||
|
||||
updateAgent(_agent)
|
||||
syncAgentToAssistant(_agent)
|
||||
|
||||
navigate(-1)
|
||||
}
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
const prompt = `你是一个专业的 prompt 优化助手,我会给你一段prompt,你需要帮我优化它,仅回复优化后的 prompt 不要添加任何解释,使用 [CRISPE提示框架] 回复。`
|
||||
|
||||
const name = formRef.current?.getFieldValue('name')
|
||||
const content = formRef.current?.getFieldValue('prompt')
|
||||
const promptText = content || name
|
||||
|
||||
if (!promptText) {
|
||||
return
|
||||
}
|
||||
|
||||
if (content) {
|
||||
navigator.clipboard.writeText(content)
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const prefixedContent = `请帮我优化下面这段 prompt,使用 CRISPE 提示框架,请使用 Markdown 格式回复,不要使用 codeblock: ${promptText}`
|
||||
const generatedText = await fetchGenerate({ prompt, content: prefixedContent })
|
||||
formRef.current?.setFieldValue('prompt', generatedText)
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (agent) {
|
||||
form.setFieldsValue({
|
||||
name: agent.name,
|
||||
prompt: agent.prompt
|
||||
})
|
||||
}
|
||||
}, [agent, form])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar>
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('agents.edit.title')}</NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer id="content-container">
|
||||
<Form
|
||||
ref={formRef}
|
||||
layout="vertical"
|
||||
form={form}
|
||||
labelAlign="left"
|
||||
colon={false}
|
||||
style={{ width: '100%' }}
|
||||
onFinish={onFinish}>
|
||||
<Form.Item name="name" label="Emoji">
|
||||
<Popover content={<EmojiPicker onEmojiClick={setEmoji} />} arrow placement="rightBottom">
|
||||
<Button icon={emoji && <span style={{ fontSize: 20 }}>{emoji}</span>}>{t('common.select')}</Button>
|
||||
</Popover>
|
||||
</Form.Item>
|
||||
<Form.Item name="name" label={t('agents.add.name')} rules={[{ required: true }]}>
|
||||
<Input placeholder={t('agents.add.name.placeholder')} spellCheck={false} allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="prompt"
|
||||
label={
|
||||
<>
|
||||
{t('agents.add.prompt')}{' '}
|
||||
<Button
|
||||
size="small"
|
||||
style={{ marginLeft: 5 }}
|
||||
type="text"
|
||||
icon={loading ? <LoadingOutlined /> : <ThunderboltOutlined />}
|
||||
onClick={handleButtonClick}
|
||||
disabled={loading}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
rules={[{ required: true }]}
|
||||
style={{ position: 'relative' }}>
|
||||
<TextArea placeholder={t('agents.add.prompt.placeholder')} spellCheck={false} rows={10} />
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ span: 16 }}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
<Button type="link" onClick={() => navigate(-1)}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<div style={{ minHeight: 50 }} />
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
overflow-y: scroll;
|
||||
`
|
||||
|
||||
export default AgentEditPage
|
||||
161
src/renderer/src/pages/agents/Agents.tsx
Normal file
161
src/renderer/src/pages/agents/Agents.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
import { DeleteOutlined, EditOutlined, MoreOutlined, PlusOutlined } from '@ant-design/icons'
|
||||
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { createAssistantFromAgent } from '@renderer/services/assistant'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { Button, Dropdown, Typography } from 'antd'
|
||||
import { ItemType } from 'antd/es/menu/interface'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AddAgentPopup from './components/AddAgentPopup'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
interface Props {
|
||||
onClick: (agent: Agent) => void
|
||||
}
|
||||
|
||||
const Agents: React.FC<Props> = ({ onClick }) => {
|
||||
const { t } = useTranslation()
|
||||
const { agents, removeAgent, updateAgents } = useAgents()
|
||||
const [dragging, setDragging] = useState(false)
|
||||
|
||||
const getMenuItems = useCallback(
|
||||
(agent: Agent) =>
|
||||
[
|
||||
{
|
||||
label: t('agents.edit.title'),
|
||||
key: 'edit',
|
||||
icon: <EditOutlined />,
|
||||
onClick: () => AssistantSettingsPopup.show({ assistant: agent })
|
||||
},
|
||||
{
|
||||
label: t('agents.add.title'),
|
||||
key: 'add',
|
||||
icon: <PlusOutlined />,
|
||||
onClick: () => createAssistantFromAgent(agent)
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
label: t('common.delete'),
|
||||
key: 'delete',
|
||||
icon: <DeleteOutlined />,
|
||||
danger: true,
|
||||
onClick: () => {
|
||||
window.modal.confirm({
|
||||
centered: true,
|
||||
content: t('agents.delete.popup.content'),
|
||||
onOk: () => removeAgent(agent.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
] as ItemType[],
|
||||
[removeAgent, t]
|
||||
)
|
||||
|
||||
return (
|
||||
<Container style={{ paddingBottom: dragging ? 30 : 0 }}>
|
||||
{agents.length > 0 && (
|
||||
<DragableList
|
||||
list={agents}
|
||||
onUpdate={updateAgents}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(agent: Agent) => (
|
||||
<Dropdown menu={{ items: getMenuItems(agent) }} trigger={['contextMenu']}>
|
||||
<AgentItem onClick={() => onClick(agent)}>
|
||||
<HStack alignItems="center" justifyContent="space-between" h="36px">
|
||||
<AgentItemName className="text-nowrap">
|
||||
{agent.emoji} {agent.name}
|
||||
</AgentItemName>
|
||||
<ActionButton className="actions" gap="15px" onClick={(e) => e.stopPropagation()}>
|
||||
<Dropdown menu={{ items: getMenuItems(agent) }} trigger={['hover']}>
|
||||
<MoreOutlined style={{ cursor: 'pointer' }} />
|
||||
</Dropdown>
|
||||
</ActionButton>
|
||||
</HStack>
|
||||
<AgentItemPrompt>{agent.prompt}</AgentItemPrompt>
|
||||
</AgentItem>
|
||||
</Dropdown>
|
||||
)}
|
||||
</DragableList>
|
||||
)}
|
||||
{!dragging && (
|
||||
<Button
|
||||
type="dashed"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => AddAgentPopup.show()}
|
||||
style={{ borderRadius: 20, height: 34 }}>
|
||||
{t('agents.add.title')}
|
||||
</Button>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 280px;
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
overflow-y: scroll;
|
||||
`
|
||||
|
||||
const AgentItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 12px;
|
||||
min-height: 38px;
|
||||
border-radius: 10px;
|
||||
user-select: none;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 12px;
|
||||
border: 0.5px solid var(--color-border);
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
.actions {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 0.5px solid var(--color-primary);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`
|
||||
|
||||
const AgentItemName = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const AgentItemPrompt = styled.div`
|
||||
font-size: 12px;
|
||||
color: var(--color-text-soft);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-top: -5px;
|
||||
color: var(--color-text-3);
|
||||
`
|
||||
|
||||
const ActionButton = styled(HStack)`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: none;
|
||||
background-color: var(--color-background-soft);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
color: var(--color-icon);
|
||||
`
|
||||
|
||||
export default Agents
|
||||
@ -1,48 +1,56 @@
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import { VStack } from '@renderer/components/Layout'
|
||||
import Agents from '@renderer/config/agents.json'
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { covertAgentToAssistant } from '@renderer/services/assistant'
|
||||
import SystemAgents from '@renderer/config/agents.json'
|
||||
import { createAssistantFromAgent } from '@renderer/services/assistant'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { Col, Row, Typography } from 'antd'
|
||||
import { find, groupBy } from 'lodash'
|
||||
import { groupBy, omit } from 'lodash'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Agents from './Agents'
|
||||
import AgentCard from './components/AgentCard'
|
||||
import MyAgents from './components/MyAgents'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
const AppsPage: FC = () => {
|
||||
const { assistants, addAssistant } = useAssistants()
|
||||
const agentGroups = groupBy(Agents, 'group')
|
||||
const AgentsPage: FC = () => {
|
||||
const agentGroups = groupBy(SystemAgents, 'group')
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onAddAgentConfirm = (agent: Agent) => {
|
||||
const added = find(assistants, { id: agent.id })
|
||||
const getAgentName = (agent: Agent) => {
|
||||
return agent.emoji ? agent.emoji + ' ' + agent.name : agent.name
|
||||
}
|
||||
|
||||
const onAddAgentConfirm = (agent: Agent) => {
|
||||
window.modal.confirm({
|
||||
title: agent.emoji + ' ' + agent.name,
|
||||
content: (agent.description || agent.prompt).substring(0, 1000) + '...',
|
||||
title: getAgentName(agent),
|
||||
content: (
|
||||
<AgentPrompt>
|
||||
<ReactMarkdown className="markdown">{agent.description || agent.prompt}</ReactMarkdown>
|
||||
</AgentPrompt>
|
||||
),
|
||||
width: 600,
|
||||
icon: null,
|
||||
closable: true,
|
||||
maskClosable: true,
|
||||
centered: true,
|
||||
okButtonProps: { type: 'primary', disabled: Boolean(added) },
|
||||
okText: added ? t('button.added') : t('button.add'),
|
||||
onOk: () => onAddAgent(agent)
|
||||
okButtonProps: { type: 'primary' },
|
||||
okText: t('agents.add.button'),
|
||||
onOk: () => createAssistantFromAgent(agent)
|
||||
})
|
||||
}
|
||||
|
||||
const onAddAgent = (agent: Agent) => {
|
||||
addAssistant(covertAgentToAssistant(agent))
|
||||
window.message.success({
|
||||
content: t('message.assistant.added.content'),
|
||||
key: 'agent-added',
|
||||
style: { marginTop: '5vh' }
|
||||
})
|
||||
const getAgentFromSystemAgent = (agent: (typeof SystemAgents)[number]) => {
|
||||
return {
|
||||
...omit(agent, 'group'),
|
||||
name: agent.name,
|
||||
id: uuid(),
|
||||
topics: [],
|
||||
type: 'agent'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@ -51,7 +59,7 @@ const AppsPage: FC = () => {
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('agents.title')}</NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer id="content-container">
|
||||
<MyAgents onClick={onAddAgentConfirm} />
|
||||
<Agents onClick={onAddAgentConfirm} />
|
||||
<AssistantsContainer>
|
||||
<VStack style={{ flex: 1 }}>
|
||||
{Object.keys(agentGroups)
|
||||
@ -65,7 +73,10 @@ const AppsPage: FC = () => {
|
||||
{agentGroups[group].map((agent, index) => {
|
||||
return (
|
||||
<Col span={8} key={group + index}>
|
||||
<AgentCard onClick={() => onAddAgentConfirm(agent)} agent={agent as any} />
|
||||
<AgentCard
|
||||
onClick={() => onAddAgentConfirm(getAgentFromSystemAgent(agent))}
|
||||
agent={agent as any}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
@ -104,4 +115,10 @@ const AssistantsContainer = styled.div`
|
||||
overflow-y: scroll;
|
||||
`
|
||||
|
||||
export default AppsPage
|
||||
const AgentPrompt = styled.div`
|
||||
max-height: 60vh;
|
||||
overflow-y: scroll;
|
||||
max-width: 560px;
|
||||
`
|
||||
|
||||
export default AgentsPage
|
||||
|
||||
@ -3,8 +3,10 @@ import 'emoji-picker-element'
|
||||
import { LoadingOutlined, ThunderboltOutlined } from '@ant-design/icons'
|
||||
import EmojiPicker from '@renderer/components/EmojiPicker'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { AGENT_PROMPT } from '@renderer/config/prompts'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { fetchGenerate } from '@renderer/services/api'
|
||||
import { getDefaultModel } from '@renderer/services/assistant'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { getLeadingEmoji, uuid } from '@renderer/utils'
|
||||
import { Button, Form, FormInstance, Input, Modal, Popover } from 'antd'
|
||||
@ -38,12 +40,15 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
return
|
||||
}
|
||||
|
||||
const _agent = {
|
||||
const _agent: Agent = {
|
||||
id: uuid(),
|
||||
name: values.name,
|
||||
emoji: _emoji,
|
||||
prompt: values.prompt,
|
||||
group: 'user'
|
||||
defaultModel: getDefaultModel(),
|
||||
type: 'agent',
|
||||
topics: [],
|
||||
messages: []
|
||||
}
|
||||
|
||||
addAgent(_agent)
|
||||
@ -60,8 +65,6 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
}
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
const prompt = `你是一个专业的 prompt 优化助手,我会给你一段prompt,你需要帮我优化它,仅回复优化后的 prompt 不要添加任何解释,使用 [CRISPE提示框架] 回复。`
|
||||
|
||||
const name = formRef.current?.getFieldValue('name')
|
||||
const content = formRef.current?.getFieldValue('prompt')
|
||||
const promptText = content || name
|
||||
@ -77,8 +80,10 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const prefixedContent = `请帮我优化下面这段 prompt,使用 CRISPE 提示框架,请使用 Markdown 格式回复,不要使用 codeblock: ${promptText}`
|
||||
const generatedText = await fetchGenerate({ prompt, content: prefixedContent })
|
||||
const generatedText = await fetchGenerate({
|
||||
prompt: AGENT_PROMPT,
|
||||
content: promptText
|
||||
})
|
||||
formRef.current?.setFieldValue('prompt', generatedText)
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error)
|
||||
@ -95,7 +100,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
onCancel={onCancel}
|
||||
maskClosable={false}
|
||||
afterClose={onClose}
|
||||
okText={t('agents.add.button')}
|
||||
okText={t('agents.add.title')}
|
||||
centered>
|
||||
<Form
|
||||
ref={formRef}
|
||||
|
||||
@ -32,7 +32,8 @@ const Container = styled.div`
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
&:hover {
|
||||
background-color: var(--color-background-mute);
|
||||
border: 0.5px solid var(--color-primary);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`
|
||||
const EmojiHeader = styled.div`
|
||||
@ -69,6 +70,7 @@ const AgentCardPrompt = styled.div`
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
|
||||
@ -1,98 +0,0 @@
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import { Box, HStack } from '@renderer/components/Layout'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { Button, Popconfirm, Typography } from 'antd'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AddAgentPopup from './AddAgentPopup'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
interface Props {
|
||||
onClick: (agent: Agent) => void
|
||||
}
|
||||
|
||||
const MyAssistants: React.FC<Props> = ({ onClick }) => {
|
||||
const { t } = useTranslation()
|
||||
const { agents, removeAgent, updateAgents } = useAgents()
|
||||
const [dragging, setDragging] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<Container style={{ paddingBottom: dragging ? 30 : 0 }}>
|
||||
<Title level={5} style={{ marginLeft: 10 }}>
|
||||
{t('agents.my_agents')}
|
||||
</Title>
|
||||
{agents.length > 0 && (
|
||||
<DragableList
|
||||
list={agents}
|
||||
onUpdate={updateAgents}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(agent) => (
|
||||
<AgentItem onClick={() => onClick(agent)}>
|
||||
<Box mr={8}>
|
||||
{agent.emoji} {agent.name}
|
||||
</Box>
|
||||
<HStack gap="15px" onClick={(e) => e.stopPropagation()}>
|
||||
<Popconfirm
|
||||
title={t('agents.delete.popup.content')}
|
||||
placement="bottom"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => removeAgent(agent)}>
|
||||
<DeleteOutlined style={{ color: 'var(--color-error)' }} />
|
||||
</Popconfirm>
|
||||
<EditOutlined style={{ cursor: 'pointer' }} onClick={() => navigate(`/agents/${agent.id}`)} />
|
||||
</HStack>
|
||||
</AgentItem>
|
||||
)}
|
||||
</DragableList>
|
||||
)}
|
||||
{!dragging && (
|
||||
<Button
|
||||
type="dashed"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => AddAgentPopup.show()}
|
||||
style={{ borderRadius: 20, height: 34 }}>
|
||||
{t('agents.add.title')}
|
||||
</Button>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: var(--assistants-width);
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
overflow-y: scroll;
|
||||
`
|
||||
|
||||
const AgentItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
user-select: none;
|
||||
background-color: var(--color-background-soft);
|
||||
margin-bottom: 8px;
|
||||
.anticon {
|
||||
font-size: 16px;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-background-mute);
|
||||
}
|
||||
`
|
||||
|
||||
export default MyAssistants
|
||||
@ -118,6 +118,7 @@ const Header = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 20px;
|
||||
padding-top: 10px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
@ -13,7 +13,7 @@ import db from '@renderer/databases'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useRuntime, useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||
import { addAssistantMessagesToTopic, getDefaultTopic } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import FileManager from '@renderer/services/file'
|
||||
import { estimateTextTokens as estimateTxtTokens } from '@renderer/services/tokens'
|
||||
@ -45,7 +45,7 @@ let _files: FileType[] = []
|
||||
const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const [text, setText] = useState(_text)
|
||||
const [inputFocus, setInputFocus] = useState(false)
|
||||
const { addTopic, model } = useAssistant(assistant.id)
|
||||
const { addTopic, model, setModel } = useAssistant(assistant.id)
|
||||
const { sendMessageShortcut, fontSize, pasteLongTextAsFile, showInputEstimatedTokens } = useSettings()
|
||||
const [expended, setExpend] = useState(false)
|
||||
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
||||
@ -127,14 +127,20 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const addNewTopic = useCallback(() => {
|
||||
const addNewTopic = useCallback(async () => {
|
||||
const topic = getDefaultTopic(assistant.id)
|
||||
|
||||
await db.topics.add({ id: topic.id, messages: [] })
|
||||
await addAssistantMessagesToTopic({ assistant, topic })
|
||||
|
||||
// Reset to assistant default model
|
||||
if (assistant.settings?.autoResetModel) {
|
||||
assistant.defaultModel && setModel(assistant.defaultModel)
|
||||
}
|
||||
|
||||
addTopic(topic)
|
||||
setActiveTopic(topic)
|
||||
|
||||
db.topics.add({ id: topic.id, messages: [] })
|
||||
}, [addTopic, assistant.id, setActiveTopic])
|
||||
}, [addTopic, assistant, setActiveTopic, setModel])
|
||||
|
||||
const clearTopic = async () => {
|
||||
if (generating) {
|
||||
@ -270,9 +276,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
_setEstimateTokenCount(tokensCount)
|
||||
setContextCount(contextCount)
|
||||
}),
|
||||
EventEmitter.on(EVENT_NAMES.ADD_NEW_TOPIC, () => {
|
||||
addNewTopic()
|
||||
})
|
||||
EventEmitter.on(EVENT_NAMES.ADD_NEW_TOPIC, addNewTopic)
|
||||
]
|
||||
return () => unsubscribes.forEach((unsub) => unsub())
|
||||
}, [addNewTopic])
|
||||
|
||||
@ -20,11 +20,20 @@ interface Props {
|
||||
total?: number
|
||||
lastMessage?: boolean
|
||||
showMenu?: boolean
|
||||
hidePresetMessages?: boolean
|
||||
onEditMessage?: (message: Message) => void
|
||||
onDeleteMessage?: (message: Message) => void
|
||||
}
|
||||
|
||||
const MessageItem: FC<Props> = ({ message, index, lastMessage, showMenu = true, onEditMessage, onDeleteMessage }) => {
|
||||
const MessageItem: FC<Props> = ({
|
||||
message,
|
||||
index,
|
||||
lastMessage,
|
||||
showMenu = true,
|
||||
hidePresetMessages,
|
||||
onEditMessage,
|
||||
onDeleteMessage
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { assistant, setModel } = useAssistant(message.assistantId)
|
||||
const model = useModel(message.modelId)
|
||||
@ -59,6 +68,10 @@ const MessageItem: FC<Props> = ({ message, index, lastMessage, showMenu = true,
|
||||
return () => unsubscribes.forEach((unsub) => unsub())
|
||||
}, [message])
|
||||
|
||||
if (hidePresetMessages && message.isPreset) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (message.type === 'clear') {
|
||||
return (
|
||||
<Divider dashed style={{ padding: '0 20px' }} plain>
|
||||
|
||||
@ -227,6 +227,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
key={message.id}
|
||||
message={message}
|
||||
index={index}
|
||||
hidePresetMessages={assistant.settings?.hideMessages}
|
||||
onEditMessage={onEditMessage}
|
||||
onDeleteMessage={onDeleteMessage}
|
||||
/>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import AssistantSettingPopup from '@renderer/components/AssistantSettings'
|
||||
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -18,7 +18,7 @@ const Prompt: FC<Props> = ({ assistant }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Container onClick={() => AssistantSettingPopup.show({ assistant })}>
|
||||
<Container onClick={() => AssistantSettingsPopup.show({ assistant })}>
|
||||
<Text>{prompt}</Text>
|
||||
</Container>
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { FormOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||
import AssistantSettingPopup from '@renderer/components/AssistantSettings'
|
||||
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { isMac, isWindows } from '@renderer/config/constant'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
@ -57,7 +57,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
||||
<TitleText
|
||||
style={{ marginRight: 10, cursor: 'pointer' }}
|
||||
className="nodrag"
|
||||
onClick={() => AssistantSettingPopup.show({ assistant })}>
|
||||
onClick={() => AssistantSettingsPopup.show({ assistant })}>
|
||||
{assistant.name}
|
||||
</TitleText>
|
||||
<SelectModelButton assistant={assistant} />
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { DeleteOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
|
||||
import AssistantSettingPopup from '@renderer/components/AssistantSettings'
|
||||
import { DeleteOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined, SaveOutlined } from '@ant-design/icons'
|
||||
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||
@ -12,7 +13,7 @@ import { Assistant } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { Dropdown, Input, InputRef } from 'antd'
|
||||
import { ItemType } from 'antd/es/menu/interface'
|
||||
import { isEmpty, last } from 'lodash'
|
||||
import { isEmpty, last, omit } from 'lodash'
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@ -39,6 +40,7 @@ const Assistants: FC<Props> = ({
|
||||
const searchRef = useRef<InputRef>(null)
|
||||
const { t } = useTranslation()
|
||||
const dispatch = useAppDispatch()
|
||||
const { addAgent } = useAgents()
|
||||
|
||||
const onDelete = useCallback(
|
||||
(assistant: Assistant) => {
|
||||
@ -53,13 +55,13 @@ const Assistants: FC<Props> = ({
|
||||
(assistant: Assistant) =>
|
||||
[
|
||||
{
|
||||
label: t('common.edit'),
|
||||
label: t('assistants.edit.title'),
|
||||
key: 'edit',
|
||||
icon: <EditOutlined />,
|
||||
onClick: () => AssistantSettingPopup.show({ assistant })
|
||||
onClick: () => AssistantSettingsPopup.show({ assistant })
|
||||
},
|
||||
{
|
||||
label: t('common.duplicate'),
|
||||
label: t('assistants.copy.title'),
|
||||
key: 'duplicate',
|
||||
icon: <CopyIcon />,
|
||||
onClick: async () => {
|
||||
@ -69,29 +71,48 @@ const Assistants: FC<Props> = ({
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('chat.topics.delete.all.title'),
|
||||
key: 'delete-all',
|
||||
label: t('assistants.clear.title'),
|
||||
key: 'clear',
|
||||
icon: <MinusCircleOutlined />,
|
||||
onClick: () => {
|
||||
window.modal.confirm({
|
||||
title: t('chat.topics.delete.all.title'),
|
||||
content: t('chat.topics.delete.all.content'),
|
||||
title: t('assistants.clear.title'),
|
||||
content: t('assistants.clear.content'),
|
||||
centered: true,
|
||||
okButtonProps: { danger: true },
|
||||
onOk: removeAllTopics
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('assistants.saveto.title'),
|
||||
key: 'save-to-agent',
|
||||
icon: <SaveOutlined />,
|
||||
onClick: async () => {
|
||||
const agent = omit(assistant, ['model', 'emoji'])
|
||||
agent.id = uuid()
|
||||
agent.type = 'agent'
|
||||
addAgent(agent)
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
label: t('common.delete'),
|
||||
key: 'delete',
|
||||
icon: <DeleteOutlined />,
|
||||
danger: true,
|
||||
onClick: () => onDelete(assistant)
|
||||
onClick: () => {
|
||||
window.modal.confirm({
|
||||
title: t('assistants.delete.title'),
|
||||
content: t('assistants.delete.content'),
|
||||
centered: true,
|
||||
okButtonProps: { danger: true },
|
||||
onOk: () => onDelete(assistant)
|
||||
})
|
||||
}
|
||||
}
|
||||
] as ItemType[],
|
||||
[addAssistant, onDelete, removeAllTopics, setActiveAssistant, t]
|
||||
[addAgent, addAssistant, onDelete, removeAllTopics, setActiveAssistant, t]
|
||||
)
|
||||
|
||||
const onSwitchAssistant = useCallback(
|
||||
|
||||
@ -49,13 +49,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
} = useSettings()
|
||||
|
||||
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
||||
updateAssistantSettings({
|
||||
temperature: settings.temperature ?? temperature,
|
||||
contextCount: settings.contextCount ?? contextCount,
|
||||
enableMaxTokens: settings.enableMaxTokens ?? enableMaxTokens,
|
||||
maxTokens: settings.maxTokens ?? maxTokens,
|
||||
streamOutput: settings.streamOutput ?? streamOutput
|
||||
})
|
||||
updateAssistantSettings(settings)
|
||||
}
|
||||
|
||||
const onTemperatureChange = (value) => {
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { fetchMessagesSummary } from '@renderer/services/api'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
@ -16,6 +17,7 @@ import store, { useAppSelector } from '@renderer/store'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Dropdown, MenuProps } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { findIndex } from 'lodash'
|
||||
import { FC, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -32,6 +34,9 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
const { assistant, removeTopic, moveTopic, updateTopic, updateTopics } = useAssistant(_assistant.id)
|
||||
const { t } = useTranslation()
|
||||
const generating = useAppSelector((state) => state.runtime.generating)
|
||||
const { showTopicTime } = useSettings()
|
||||
|
||||
const borderRadius = showTopicTime ? 12 : 17
|
||||
|
||||
const onDeleteTopic = useCallback(
|
||||
(topic: Topic) => {
|
||||
@ -172,8 +177,12 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
const isActive = topic.id === activeTopic?.id
|
||||
return (
|
||||
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
|
||||
<TopicListItem className={isActive ? 'active' : ''} onClick={() => onSwitchTopic(topic)}>
|
||||
<TopicListItem
|
||||
className={isActive ? 'active' : ''}
|
||||
style={{ borderRadius }}
|
||||
onClick={() => onSwitchTopic(topic)}>
|
||||
<TopicName className="name">{topic.name.replace('`', '')}</TopicName>
|
||||
{showTopicTime && <TopicTime>{dayjs(topic.createdAt).format('MM/DD HH:mm')}</TopicTime>}
|
||||
{isActive && (
|
||||
<MenuButton
|
||||
className="menu"
|
||||
@ -192,6 +201,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
)
|
||||
}}
|
||||
</DragableList>
|
||||
<div style={{ minHeight: '10px' }}></div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@ -210,9 +220,8 @@ const TopicListItem = styled.div`
|
||||
font-family: Ubuntu;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
font-family: Ubuntu;
|
||||
cursor: pointer;
|
||||
@ -249,6 +258,11 @@ const TopicName = styled.div`
|
||||
font-size: 13px;
|
||||
`
|
||||
|
||||
const TopicTime = styled.div`
|
||||
color: var(--color-text-3);
|
||||
font-size: 11px;
|
||||
`
|
||||
|
||||
const MenuButton = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@ -3,8 +3,11 @@ import { FileProtectOutlined, GlobalOutlined, MailOutlined, SoundOutlined } from
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import MinApp from '@renderer/components/MinApp'
|
||||
import { APP_NAME, AppLogo } from '@renderer/config/env'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setManualUpdateCheck } from '@renderer/store/settings'
|
||||
import { runAsyncFunction } from '@renderer/utils'
|
||||
import { Avatar, Button, Progress, Row, Tag } from 'antd'
|
||||
import { Avatar, Button, Progress, Row, Switch, Tag } from 'antd'
|
||||
import { ProgressInfo } from 'electron-updater'
|
||||
import { debounce } from 'lodash'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
@ -20,6 +23,8 @@ const AboutSettings: FC = () => {
|
||||
const [percent, setPercent] = useState(0)
|
||||
const [checkUpdateLoading, setCheckUpdateLoading] = useState(false)
|
||||
const [downloading, setDownloading] = useState(false)
|
||||
const { manualUpdateCheck } = useSettings()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const onCheckUpdate = debounce(
|
||||
async () => {
|
||||
@ -140,6 +145,11 @@ const AboutSettings: FC = () => {
|
||||
</CheckUpdateButton>
|
||||
</AboutHeader>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.general.manually_check_update.title')}</SettingRowTitle>
|
||||
<Switch value={manualUpdateCheck} onChange={(v) => dispatch(setManualUpdateCheck(v))} />
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
<SoundOutlined />
|
||||
|
||||
@ -24,7 +24,7 @@ const DataSettings: FC = () => {
|
||||
<SettingRowTitle>{t('settings.data.webdav.title')}</SettingRowTitle>
|
||||
<VStack gap="5px">
|
||||
<Link to="/settings/data/webdav" style={{ color: 'var(--color-text-2)' }}>
|
||||
{t('settings.general.view_webdav_settings')}
|
||||
<Button>{t('settings.general.view_webdav_settings')}</Button>
|
||||
</Link>
|
||||
</VStack>
|
||||
</SettingRow>
|
||||
|
||||
@ -2,7 +2,7 @@ import { isMac } from '@renderer/config/constant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setClickAssistantToShowTopic, setLanguage, setManualUpdateCheck } from '@renderer/store/settings'
|
||||
import { setClickAssistantToShowTopic, setLanguage, setShowTopicTime } from '@renderer/store/settings'
|
||||
import { setProxyUrl as _setProxyUrl } from '@renderer/store/settings'
|
||||
import { ThemeMode } from '@renderer/types'
|
||||
import { isValidProxyUrl } from '@renderer/utils'
|
||||
@ -19,8 +19,8 @@ const GeneralSettings: FC = () => {
|
||||
theme,
|
||||
windowStyle,
|
||||
topicPosition,
|
||||
showTopicTime,
|
||||
clickAssistantToShowTopic,
|
||||
manualUpdateCheck,
|
||||
setTheme,
|
||||
setWindowStyle,
|
||||
setTopicPosition
|
||||
@ -95,19 +95,6 @@ const GeneralSettings: FC = () => {
|
||||
</>
|
||||
)}
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.general.check_update_setting')}</SettingRowTitle>
|
||||
<Select
|
||||
defaultValue={manualUpdateCheck ?? false}
|
||||
style={{ width: 180 }}
|
||||
onChange={(v) => dispatch(setManualUpdateCheck(v))}
|
||||
options={[
|
||||
{ value: false, label: t('settings.general.auto_update_check') },
|
||||
{ value: true, label: t('settings.general.manual_update_check') }
|
||||
]}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.proxy.title')}</SettingRowTitle>
|
||||
<Input
|
||||
@ -145,6 +132,11 @@ const GeneralSettings: FC = () => {
|
||||
<SettingDivider />
|
||||
</>
|
||||
)}
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.topic.show.time')}</SettingRowTitle>
|
||||
<Switch checked={showTopicTime} onChange={(checked) => dispatch(setShowTopicTime(checked))} />
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
</SettingContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ const ProvidersList: FC = () => {
|
||||
{getFirstCharacter(provider.name)}
|
||||
</ProviderLogo>
|
||||
)}
|
||||
<ProviderItemName>
|
||||
<ProviderItemName className="text-nowrap">
|
||||
{provider.isSystem ? t(`provider.${provider.id}`) : provider.name}
|
||||
</ProviderItemName>
|
||||
{provider.enabled && (
|
||||
@ -196,9 +196,6 @@ const ProviderLogo = styled(Avatar)`
|
||||
|
||||
const ProviderItemName = styled.div`
|
||||
margin-left: 10px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
font-family: Ubuntu;
|
||||
`
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources'
|
||||
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||
import { SUMMARIZE_PROMPT } from '@renderer/config/prompts'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES } from '@renderer/services/event'
|
||||
import { filterContextMessages } from '@renderer/services/messages'
|
||||
@ -136,22 +137,34 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
public async summaries(messages: Message[], assistant: Assistant): Promise<string> {
|
||||
const model = getTopNamingModel() || assistant.model || getDefaultModel()
|
||||
|
||||
const userMessages = takeRight(messages, 5).map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
const userMessages = takeRight(messages, 5)
|
||||
.filter((message) => !message.isPreset)
|
||||
.map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
|
||||
if (first(userMessages)?.role === 'assistant') {
|
||||
userMessages.shift()
|
||||
}
|
||||
|
||||
const userMessageContent = userMessages.reduce((prev, curr) => {
|
||||
const content = curr.role === 'user' ? `User: ${curr.content}` : `Assistant: ${curr.content}`
|
||||
return prev + (prev ? '\n' : '') + content
|
||||
}, '')
|
||||
|
||||
const systemMessage = {
|
||||
role: 'system',
|
||||
content: '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
|
||||
content: SUMMARIZE_PROMPT
|
||||
}
|
||||
|
||||
const userMessage = {
|
||||
role: 'user',
|
||||
content: userMessageContent
|
||||
}
|
||||
|
||||
const message = await this.sdk.messages.create({
|
||||
messages: userMessages as Anthropic.Messages.MessageParam[],
|
||||
messages: [userMessage] as Anthropic.Messages.MessageParam[],
|
||||
model: model.id,
|
||||
system: systemMessage.content,
|
||||
stream: false,
|
||||
@ -165,16 +178,16 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
const model = getDefaultModel()
|
||||
|
||||
const message = await this.sdk.messages.create({
|
||||
model: model.id,
|
||||
system: prompt,
|
||||
stream: false,
|
||||
max_tokens: 4096,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content
|
||||
}
|
||||
],
|
||||
model: model.id,
|
||||
system: prompt,
|
||||
stream: false,
|
||||
max_tokens: 4096
|
||||
]
|
||||
})
|
||||
|
||||
return message.content[0].type === 'text' ? message.content[0].text : ''
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
Part,
|
||||
TextPart
|
||||
} from '@google/generative-ai'
|
||||
import { SUMMARIZE_PROMPT } from '@renderer/config/prompts'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES } from '@renderer/services/event'
|
||||
import { filterContextMessages } from '@renderer/services/messages'
|
||||
@ -145,14 +146,26 @@ export default class GeminiProvider extends BaseProvider {
|
||||
public async summaries(messages: Message[], assistant: Assistant): Promise<string> {
|
||||
const model = getTopNamingModel() || assistant.model || getDefaultModel()
|
||||
|
||||
const userMessages = takeRight(messages, 5).map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
const userMessages = takeRight(messages, 5)
|
||||
.filter((message) => !message.isPreset)
|
||||
.map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
|
||||
const userMessageContent = userMessages.reduce((prev, curr) => {
|
||||
const content = curr.role === 'user' ? `User: ${curr.content}` : `Assistant: ${curr.content}`
|
||||
return prev + (prev ? '\n' : '') + content
|
||||
}, '')
|
||||
|
||||
const systemMessage = {
|
||||
role: 'system',
|
||||
content: '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
|
||||
content: SUMMARIZE_PROMPT
|
||||
}
|
||||
|
||||
const userMessage = {
|
||||
role: 'user',
|
||||
content: userMessageContent
|
||||
}
|
||||
|
||||
const geminiModel = this.sdk.getGenerativeModel({
|
||||
@ -163,16 +176,9 @@ export default class GeminiProvider extends BaseProvider {
|
||||
}
|
||||
})
|
||||
|
||||
const lastUserMessage = userMessages.pop()
|
||||
const chat = await geminiModel.startChat()
|
||||
|
||||
const chat = await geminiModel.startChat({
|
||||
history: userMessages.map((message) => ({
|
||||
role: message.role === 'user' ? 'user' : 'model',
|
||||
parts: [{ text: message.content }]
|
||||
}))
|
||||
})
|
||||
|
||||
const { response } = await chat.sendMessage(lastUserMessage?.content!)
|
||||
const { response } = await chat.sendMessage(userMessage.content)
|
||||
|
||||
return response.text()
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { isLocalAi } from '@renderer/config/env'
|
||||
import { isSupportedModel, isVisionModel } from '@renderer/config/models'
|
||||
import { SUMMARIZE_PROMPT } from '@renderer/config/prompts'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES } from '@renderer/services/event'
|
||||
import { filterContextMessages } from '@renderer/services/messages'
|
||||
import { Assistant, FileTypes, Message, Model, Provider, Suggestion } from '@renderer/types'
|
||||
import { removeQuotes } from '@renderer/utils'
|
||||
import { first, takeRight } from 'lodash'
|
||||
import { takeRight } from 'lodash'
|
||||
import OpenAI, { AzureOpenAI } from 'openai'
|
||||
import {
|
||||
ChatCompletionContentPart,
|
||||
@ -45,7 +45,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
}
|
||||
|
||||
private get isNotSupportFiles() {
|
||||
const providers = ['deepseek', 'baichuan', 'minimax', 'yi', 'doubao']
|
||||
const providers = ['deepseek', 'baichuan', 'minimax', 'doubao']
|
||||
return providers.includes(this.provider.id)
|
||||
}
|
||||
|
||||
@ -190,23 +190,35 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
public async summaries(messages: Message[], assistant: Assistant): Promise<string> {
|
||||
const model = getTopNamingModel() || assistant.model || getDefaultModel()
|
||||
|
||||
const userMessages = takeRight(messages, 5).map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
const userMessages = takeRight(messages, 5)
|
||||
.filter((message) => !message.isPreset)
|
||||
.map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content
|
||||
}))
|
||||
|
||||
const userMessageContent = userMessages.reduce((prev, curr) => {
|
||||
const content = curr.role === 'user' ? `User: ${curr.content}` : `Assistant: ${curr.content}`
|
||||
return prev + (prev ? '\n' : '') + content
|
||||
}, '')
|
||||
|
||||
const systemMessage = {
|
||||
role: 'system',
|
||||
content: '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
|
||||
content: SUMMARIZE_PROMPT
|
||||
}
|
||||
|
||||
const userMessage = {
|
||||
role: 'user',
|
||||
content: userMessageContent
|
||||
}
|
||||
|
||||
// @ts-ignore key is not typed
|
||||
const response = await this.sdk.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: [systemMessage, ...(isLocalAi ? [first(userMessages)] : userMessages)] as ChatCompletionMessageParam[],
|
||||
messages: [systemMessage, userMessage] as ChatCompletionMessageParam[],
|
||||
stream: false,
|
||||
max_tokens: 50,
|
||||
keep_alive: this.keepAliveTime
|
||||
keep_alive: this.keepAliveTime,
|
||||
max_tokens: 1000
|
||||
})
|
||||
|
||||
return removeQuotes(response.choices[0].message?.content?.substring(0, 50) || '')
|
||||
@ -219,8 +231,8 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
model: model.id,
|
||||
stream: false,
|
||||
messages: [
|
||||
{ role: 'user', content },
|
||||
{ role: 'system', content: prompt }
|
||||
{ role: 'system', content: prompt },
|
||||
{ role: 'user', content }
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import db from '@renderer/databases'
|
||||
import i18n from '@renderer/i18n'
|
||||
import store from '@renderer/store'
|
||||
import { updateAgent } from '@renderer/store/agents'
|
||||
import { updateAssistant } from '@renderer/store/assistants'
|
||||
import { Agent, Assistant, AssistantSettings, Model, Provider, Topic } from '@renderer/types'
|
||||
import { getLeadingEmoji, removeLeadingEmoji, uuid } from '@renderer/utils'
|
||||
import { addAssistant } from '@renderer/store/assistants'
|
||||
import { Agent, Assistant, AssistantSettings, Message, Model, Provider, Topic } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
|
||||
import { estimateMessageUsage } from './tokens'
|
||||
|
||||
export function getDefaultAssistant(): Assistant {
|
||||
return {
|
||||
id: 'default',
|
||||
name: i18n.t('chat.default.name'),
|
||||
prompt: '',
|
||||
topics: [getDefaultTopic('default')]
|
||||
topics: [getDefaultTopic('default')],
|
||||
messages: [],
|
||||
type: 'assistant'
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,19 +86,9 @@ export const getAssistantSettings = (assistant: Assistant): AssistantSettings =>
|
||||
temperature: assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE,
|
||||
enableMaxTokens: assistant?.settings?.enableMaxTokens ?? false,
|
||||
maxTokens: getAssistantMaxTokens(),
|
||||
streamOutput: assistant?.settings?.streamOutput ?? true
|
||||
}
|
||||
}
|
||||
|
||||
export function covertAgentToAssistant(agent: Agent): Assistant {
|
||||
const id = agent.group === 'system' ? uuid() : String(agent.id)
|
||||
return {
|
||||
...getDefaultAssistant(),
|
||||
...agent,
|
||||
id,
|
||||
topics: [getDefaultTopic(id)],
|
||||
name: getAssistantNameWithAgent(agent),
|
||||
settings: getDefaultAssistantSettings()
|
||||
streamOutput: assistant?.settings?.streamOutput ?? true,
|
||||
hideMessages: assistant?.settings?.hideMessages ?? false,
|
||||
autoResetModel: assistant?.settings?.autoResetModel ?? false
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,38 +96,58 @@ export function getAssistantNameWithAgent(agent: Agent) {
|
||||
return agent.emoji ? agent.emoji + ' ' + agent.name : agent.name
|
||||
}
|
||||
|
||||
export function syncAsistantToAgent(assistant: Assistant) {
|
||||
const agents = store.getState().agents.agents
|
||||
const agent = agents.find((a) => a.id === assistant.id)
|
||||
|
||||
if (agent) {
|
||||
store.dispatch(
|
||||
updateAgent({
|
||||
...agent,
|
||||
emoji: getLeadingEmoji(assistant.name),
|
||||
name: removeLeadingEmoji(assistant.name),
|
||||
prompt: assistant.prompt
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function syncAgentToAssistant(agent: Agent) {
|
||||
const assistants = store.getState().assistants.assistants
|
||||
const assistant = assistants.find((a) => a.id === agent.id)
|
||||
|
||||
if (assistant) {
|
||||
store.dispatch(
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
name: getAssistantNameWithAgent(agent),
|
||||
prompt: agent.prompt
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function getAssistantById(id: string) {
|
||||
const assistants = store.getState().assistants.assistants
|
||||
return assistants.find((a) => a.id === id)
|
||||
}
|
||||
|
||||
export async function addAssistantMessagesToTopic({ assistant, topic }: { assistant: Assistant; topic: Topic }) {
|
||||
const messages: Message[] = []
|
||||
const defaultModel = getDefaultModel()
|
||||
|
||||
for (const msg of assistant?.messages || []) {
|
||||
const message: Message = {
|
||||
id: uuid(),
|
||||
assistantId: assistant.id,
|
||||
role: msg.role,
|
||||
content: msg.content,
|
||||
topicId: topic.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
status: 'success',
|
||||
modelId: assistant.defaultModel?.id || defaultModel.id,
|
||||
type: 'text',
|
||||
isPreset: true
|
||||
}
|
||||
message.usage = await estimateMessageUsage(message)
|
||||
messages.push(message)
|
||||
}
|
||||
|
||||
db.topics.put({ id: topic.id, messages }, topic.id)
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
export async function createAssistantFromAgent(agent: Agent) {
|
||||
const assistantId = uuid()
|
||||
const topic = getDefaultTopic(assistantId)
|
||||
|
||||
const assistant: Assistant = {
|
||||
...agent,
|
||||
id: assistantId,
|
||||
name: agent.emoji ? agent.emoji + ' ' + agent.name : agent.name,
|
||||
topics: [topic],
|
||||
model: agent.defaultModel,
|
||||
type: 'assistant'
|
||||
}
|
||||
|
||||
store.dispatch(addAssistant(assistant))
|
||||
|
||||
await addAssistantMessagesToTopic({ assistant, topic })
|
||||
|
||||
window.message.success({
|
||||
content: i18n.t('message.assistant.added.content'),
|
||||
key: 'assistant-added'
|
||||
})
|
||||
|
||||
return assistant
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import { Agent, AssistantSettings } from '@renderer/types'
|
||||
|
||||
export interface AgentsState {
|
||||
agents: Agent[]
|
||||
@ -9,25 +10,49 @@ const initialState: AgentsState = {
|
||||
agents: []
|
||||
}
|
||||
|
||||
const runtimeSlice = createSlice({
|
||||
const assistantsSlice = createSlice({
|
||||
name: 'agents',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateAgents: (state, action: PayloadAction<Agent[]>) => {
|
||||
state.agents = action.payload
|
||||
},
|
||||
addAgent: (state, action: PayloadAction<Agent>) => {
|
||||
state.agents.push(action.payload)
|
||||
},
|
||||
removeAgent: (state, action: PayloadAction<Agent>) => {
|
||||
state.agents = state.agents.filter((a) => a.id !== action.payload.id)
|
||||
removeAgent: (state, action: PayloadAction<{ id: string }>) => {
|
||||
state.agents = state.agents.filter((c) => c.id !== action.payload.id)
|
||||
},
|
||||
updateAgent: (state, action: PayloadAction<Agent>) => {
|
||||
state.agents = state.agents.map((a) => (a.id === action.payload.id ? action.payload : a))
|
||||
state.agents = state.agents.map((c) => (c.id === action.payload.id ? action.payload : c))
|
||||
},
|
||||
updateAgents: (state, action: PayloadAction<Agent[]>) => {
|
||||
state.agents = action.payload
|
||||
updateAgentSettings: (
|
||||
state,
|
||||
action: PayloadAction<{ assistantId: string; settings: Partial<AssistantSettings> }>
|
||||
) => {
|
||||
for (const agent of state.agents) {
|
||||
const settings = action.payload.settings
|
||||
if (agent.id === action.payload.assistantId) {
|
||||
for (const key in settings) {
|
||||
if (!agent.settings) {
|
||||
agent.settings = {
|
||||
temperature: DEFAULT_TEMPERATURE,
|
||||
contextCount: DEFAULT_CONEXTCOUNT,
|
||||
enableMaxTokens: false,
|
||||
maxTokens: 0,
|
||||
streamOutput: true,
|
||||
hideMessages: false,
|
||||
autoResetModel: false
|
||||
}
|
||||
}
|
||||
agent.settings[key] = settings[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { addAgent, removeAgent, updateAgent, updateAgents } = runtimeSlice.actions
|
||||
export const { updateAgents, addAgent, removeAgent, updateAgent, updateAgentSettings } = assistantsSlice.actions
|
||||
|
||||
export default runtimeSlice.reducer
|
||||
export default assistantsSlice.reducer
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import { TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { getDefaultAssistant, getDefaultTopic } from '@renderer/services/assistant'
|
||||
import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
|
||||
@ -33,15 +34,29 @@ const assistantsSlice = createSlice({
|
||||
updateAssistant: (state, action: PayloadAction<Assistant>) => {
|
||||
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
|
||||
updateAssistantSettings: (
|
||||
state,
|
||||
action: PayloadAction<{ assistantId: string; settings: Partial<AssistantSettings> }>
|
||||
) => {
|
||||
for (const assistant of state.assistants) {
|
||||
const settings = action.payload.settings
|
||||
if (assistant.id === action.payload.assistantId) {
|
||||
for (const key in settings) {
|
||||
if (!assistant.settings) {
|
||||
assistant.settings = {
|
||||
temperature: DEFAULT_TEMPERATURE,
|
||||
contextCount: DEFAULT_CONEXTCOUNT,
|
||||
enableMaxTokens: false,
|
||||
maxTokens: 0,
|
||||
streamOutput: true,
|
||||
hideMessages: false,
|
||||
autoResetModel: false
|
||||
}
|
||||
}
|
||||
: assistant
|
||||
)
|
||||
assistant.settings[key] = settings[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
addTopic: (state, action: PayloadAction<{ assistantId: string; topic: Topic }>) => {
|
||||
const topic = action.payload.topic
|
||||
|
||||
@ -22,7 +22,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 32,
|
||||
version: 33,
|
||||
blacklist: ['runtime'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { SYSTEM_MODELS } from '@renderer/config/models'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { createMigrate } from 'redux-persist'
|
||||
|
||||
@ -568,6 +569,31 @@ const migrateConfig = {
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
'33': (state: RootState) => {
|
||||
state.assistants.defaultAssistant.type = 'assistant'
|
||||
|
||||
state.agents.agents.forEach((agent) => {
|
||||
agent.type = 'agent'
|
||||
// @ts-ignore eslint-disable-next-line
|
||||
delete agent.group
|
||||
})
|
||||
|
||||
return {
|
||||
...state,
|
||||
assistants: {
|
||||
...state.assistants,
|
||||
assistants: [...state.assistants.assistants].map((assistant) => {
|
||||
// @ts-ignore eslint-disable-next-line
|
||||
delete assistant.group
|
||||
return {
|
||||
...assistant,
|
||||
id: assistant.id.length === 36 ? assistant.id : uuid(),
|
||||
type: assistant.type === 'system' ? assistant.type : 'assistant'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ export interface SettingsState {
|
||||
windowStyle: 'transparent' | 'opaque'
|
||||
fontSize: number
|
||||
topicPosition: 'left' | 'right'
|
||||
showTopicTime: boolean
|
||||
pasteLongTextAsFile: boolean
|
||||
clickAssistantToShowTopic: boolean
|
||||
manualUpdateCheck: boolean
|
||||
@ -43,7 +44,8 @@ const initialState: SettingsState = {
|
||||
windowStyle: 'transparent',
|
||||
fontSize: 14,
|
||||
topicPosition: 'right',
|
||||
pasteLongTextAsFile: true,
|
||||
showTopicTime: false,
|
||||
pasteLongTextAsFile: false,
|
||||
clickAssistantToShowTopic: false,
|
||||
manualUpdateCheck: false,
|
||||
renderInputMessageAsMarkdown: true,
|
||||
@ -104,6 +106,9 @@ const settingsSlice = createSlice({
|
||||
setTopicPosition: (state, action: PayloadAction<'left' | 'right'>) => {
|
||||
state.topicPosition = action.payload
|
||||
},
|
||||
setShowTopicTime: (state, action: PayloadAction<boolean>) => {
|
||||
state.showTopicTime = action.payload
|
||||
},
|
||||
setPasteLongTextAsFile: (state, action: PayloadAction<boolean>) => {
|
||||
state.pasteLongTextAsFile = action.payload
|
||||
},
|
||||
@ -150,6 +155,7 @@ export const {
|
||||
setFontSize,
|
||||
setWindowStyle,
|
||||
setTopicPosition,
|
||||
setShowTopicTime,
|
||||
setPasteLongTextAsFile,
|
||||
setRenderInputMessageAsMarkdown,
|
||||
setClickAssistantToShowTopic,
|
||||
|
||||
@ -5,10 +5,18 @@ export type Assistant = {
|
||||
name: string
|
||||
prompt: string
|
||||
topics: Topic[]
|
||||
type: string
|
||||
emoji?: string
|
||||
description?: string
|
||||
model?: Model
|
||||
defaultModel?: Model
|
||||
settings?: AssistantSettings
|
||||
messages?: AssistantMessage[]
|
||||
}
|
||||
|
||||
export type AssistantMessage = {
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
}
|
||||
|
||||
export type AssistantSettings = {
|
||||
@ -17,8 +25,12 @@ export type AssistantSettings = {
|
||||
maxTokens: number | undefined
|
||||
enableMaxTokens: boolean
|
||||
streamOutput: boolean
|
||||
hideMessages: boolean
|
||||
autoResetModel: boolean
|
||||
}
|
||||
|
||||
export type Agent = Omit<Assistant, 'model'>
|
||||
|
||||
export type Message = {
|
||||
id: string
|
||||
assistantId: string
|
||||
@ -32,6 +44,7 @@ export type Message = {
|
||||
images?: string[]
|
||||
usage?: OpenAI.Completions.CompletionUsage
|
||||
type?: 'text' | '@' | 'clear'
|
||||
isPreset?: boolean
|
||||
}
|
||||
|
||||
export type Topic = {
|
||||
@ -70,15 +83,6 @@ export type Model = {
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type Agent = {
|
||||
id: string
|
||||
name: string
|
||||
emoji: string
|
||||
description?: string
|
||||
prompt: string
|
||||
group: string
|
||||
}
|
||||
|
||||
export type Suggestion = {
|
||||
content: string
|
||||
}
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@ -401,6 +401,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@cherry-studio/artifacts@workspace:npm/artifacts":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@cherry-studio/artifacts@workspace:npm/artifacts"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@ctrl/tinycolor@npm:^3.6.1":
|
||||
version: 3.6.1
|
||||
resolution: "@ctrl/tinycolor@npm:3.6.1"
|
||||
@ -11506,11 +11512,11 @@ __metadata:
|
||||
|
||||
"typescript@patch:typescript@npm%3A^5.3.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.6.2#optional!builtin<compat/typescript>":
|
||||
version: 5.6.2
|
||||
resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin<compat/typescript>::version=5.6.2&hash=379a07"
|
||||
resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin<compat/typescript>::version=5.6.2&hash=8c6c40"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: 10c0/e6c1662e4852e22fe4bbdca471dca3e3edc74f6f1df043135c44a18a7902037023ccb0abdfb754595ca9028df8920f2f8492c00fc3cbb4309079aae8b7de71cd
|
||||
checksum: 10c0/94eb47e130d3edd964b76da85975601dcb3604b0c848a36f63ac448d0104e93819d94c8bdf6b07c00120f2ce9c05256b8b6092d23cf5cf1c6fa911159e4d572f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user