diff --git a/.gitignore b/.gitignore
index a7450567..a38c9c13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,12 +19,6 @@ lerna-debug.log*
*.sln
*.sw?
-# NPM
-npm/*/*
-!npm/*/dist
-!npm/*/package.json
-!npm/*/*.js
-
# Yarn
.pnp.*
.yarn/*
diff --git a/npm/artifacts/README.md b/npm/artifacts/README.md
new file mode 100644
index 00000000..944e8916
--- /dev/null
+++ b/npm/artifacts/README.md
@@ -0,0 +1 @@
+# Cherry Studio Artifacts
diff --git a/npm/artifacts/package.json b/npm/artifacts/package.json
new file mode 100644
index 00000000..9adacafa
--- /dev/null
+++ b/npm/artifacts/package.json
@@ -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"
+}
diff --git a/npm/artifacts/statics/word-explanation-card.css b/npm/artifacts/statics/word-explanation-card.css
new file mode 100644
index 00000000..fadf5800
--- /dev/null
+++ b/npm/artifacts/statics/word-explanation-card.css
@@ -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;
+}
diff --git a/package.json b/package.json
index b50de592..69fbab82 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
"homepage": "https://github.com/kangfenmao/cherry-studio",
"workspaces": {
"packages": [
- "local"
+ "local",
+ "npm/*"
]
},
"scripts": {
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index c0a4cc0c..030473a2 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -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 {
} />
} />
} />
- } />
} />
} />
} />
diff --git a/src/renderer/src/assets/styles/scrollbar.scss b/src/renderer/src/assets/styles/scrollbar.scss
index e4c9e7c5..c7549a91 100644
--- a/src/renderer/src/assets/styles/scrollbar.scss
+++ b/src/renderer/src/assets/styles/scrollbar.scss
@@ -10,7 +10,6 @@
::-webkit-scrollbar-thumb {
background: var(--color-scrollbar-thumb);
- border-radius: 4px;
&:hover {
background: var(--color-scrollbar-thumb-hover);
}
diff --git a/src/renderer/src/components/AssistantSettings/AssistantMessagesSettings.tsx b/src/renderer/src/components/AssistantSettings/AssistantMessagesSettings.tsx
new file mode 100644
index 00000000..219efbe4
--- /dev/null
+++ b/src/renderer/src/components/AssistantSettings/AssistantMessagesSettings.tsx
@@ -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) => void
+}
+
+const AssistantMessagesSettings: FC = ({ assistant, updateAssistant, updateAssistantSettings }) => {
+ const { t } = useTranslation()
+ const [form] = Form.useForm()
+ const formRef = useRef(null)
+ const [messages, setMessagess] = useState(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 (
+
+
+ {
+ setHideMessages(checked)
+ updateAssistantSettings({ hideMessages: checked })
+ }}
+ />
+
+
+
+ {messages.map(
+ (_, index) =>
+ index % 2 === 0 && (
+ } type="text" danger onClick={() => deleteMessages(index)} />}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ )}
+
+ } onClick={addMessages}>
+ {t('agents.edit.message.add.title')}
+
+
+
+
+
+ {messages.length > 0 && (
+
+ )}
+
+
+
+
+ )
+}
+
+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
diff --git a/src/renderer/src/components/AssistantSettings/AssistantModelSettings.tsx b/src/renderer/src/components/AssistantSettings/AssistantModelSettings.tsx
index 12d20a36..d42fde26 100644
--- a/src/renderer/src/components/AssistantSettings/AssistantModelSettings.tsx
+++ b/src/renderer/src/components/AssistantSettings/AssistantModelSettings.tsx
@@ -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) => void
}
-const AssistantModelSettings: FC = (props) => {
- const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id)
+const AssistantModelSettings: FC = ({ 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) => {
- 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 (
+
+
+
+
+ : }
+ onClick={onSelectModel}>
+ {defaultModel ? defaultModel.name : t('agents.edit.model.select.title')}
+
+
+
+
+
+
+
+ {
+ setAutoResetModel(checked)
+ updateAssistantSettings({ autoResetModel: checked })
+ }}
+ />
+
+
@@ -95,10 +114,12 @@ const AssistantModelSettings: FC = (props) => {
-
-
-
-
+
@@ -123,7 +144,7 @@ const AssistantModelSettings: FC = (props) => {
checked={enableMaxTokens}
onChange={(enabled) => {
setEnableMaxTokens(enabled)
- onUpdateAssistantSettings({ enableMaxTokens: enabled })
+ updateAssistantSettings({ enableMaxTokens: enabled })
}}
/>
@@ -141,18 +162,17 @@ const AssistantModelSettings: FC = (props) => {
- {t('model.stream_output')}
+
{
setStreamOutput(checked)
- onUpdateAssistantSettings({ streamOutput: checked })
+ updateAssistantSettings({ streamOutput: checked })
}}
/>
-
+
+
@@ -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
diff --git a/src/renderer/src/components/AssistantSettings/AssistantPromptSettings.tsx b/src/renderer/src/components/AssistantSettings/AssistantPromptSettings.tsx
index 2bedcd19..fee30d33 100644
--- a/src/renderer/src/components/AssistantSettings/AssistantPromptSettings.tsx
+++ b/src/renderer/src/components/AssistantSettings/AssistantPromptSettings.tsx
@@ -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 = ({ 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 (
-
+
{t('common.name')}
@@ -43,12 +47,20 @@ const AssistantPromptSettings: React.FC<{ assistant: Assistant; onOk: () => void
style={{ minHeight: 'calc(80vh - 200px)', maxHeight: 'calc(80vh - 150px)' }}
/>
-
-
+
)
}
+const Container = styled.div`
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ overflow: hidden;
+ padding: 5px;
+`
+
export default AssistantPromptSettings
diff --git a/src/renderer/src/components/AssistantSettings/index.tsx b/src/renderer/src/components/AssistantSettings/index.tsx
index 8299cc41..122261fc 100644
--- a/src/renderer/src/components/AssistantSettings/index.tsx
+++ b/src/renderer/src/components/AssistantSettings/index.tsx
@@ -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 = ({ assistant, resolve }) => {
+const AssistantSettingPopupContainer: React.FC = ({ 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 = ({ assistant, resolve })
= ({ assistant, resolve })
/>
- {menu === 'prompt' && }
- {menu === 'model' && }
+ {menu === 'prompt' && (
+
+ )}
+ {menu === 'model' && (
+
+ )}
+ {menu === 'messages' && (
+
+ )}
@@ -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((resolve) => {
TopView.show(
@@ -144,10 +171,10 @@ export default class AssistantSettingPopup {
{...props}
resolve={(v) => {
resolve(v)
- this.hide()
+ TopView.hide('AssistantSettingsPopup')
}}
/>,
- 'AssistantSettingPopup'
+ 'AssistantSettingsPopup'
)
})
}
diff --git a/src/renderer/src/components/Popups/AddAssistantPopup.tsx b/src/renderer/src/components/Popups/AddAssistantPopup.tsx
index f8156324..6a922ee5 100644
--- a/src/renderer/src/components/Popups/AddAssistantPopup.tsx
+++ b/src/renderer/src/components/Popups/AddAssistantPopup.tsx
@@ -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 = ({ resolve }) => {
const { assistants, addAssistant } = useAssistants()
const inputRef = useRef(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 = ({ resolve }) => {
{agent.emoji} {agent.name}
- {agent.group === 'system' && {t('agents.tag.system')}}
- {agent.group === 'user' && {t('agents.tag.user')}}
+ {agent.id === 'default' && {t('agents.tag.system')}}
+ {agent.type === 'agent' && {t('agents.tag.agent')}}
))}
diff --git a/src/renderer/src/components/Popups/SelectModelPopup.tsx b/src/renderer/src/components/Popups/SelectModelPopup.tsx
index 478115f0..937ebdb8 100644
--- a/src/renderer/src/components/Popups/SelectModelPopup.tsx
+++ b/src/renderer/src/components/Popups/SelectModelPopup.tsx
@@ -80,7 +80,6 @@ const PopupContainer: React.FC = ({ 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}>
diff --git a/src/renderer/src/config/prompts.ts b/src/renderer/src/config/prompts.ts
new file mode 100644
index 00000000..63157654
--- /dev/null
+++ b/src/renderer/src/config/prompts.ts
@@ -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 个字以内的标题,不要使用标点符号和其他特殊符号。'
diff --git a/src/renderer/src/hooks/useAgents.ts b/src/renderer/src/hooks/useAgents.ts
index 87a4753b..238f04ec 100644
--- a/src/renderer/src/hooks/useAgents.ts
+++ b/src/renderer/src/hooks/useAgents.ts
@@ -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) => {
+ dispatch(updateAgentSettings({ assistantId: agent.id, settings }))
+ }
}
}
diff --git a/src/renderer/src/hooks/useAssistant.ts b/src/renderer/src/hooks/useAssistant.ts
index bc4007cc..856fffd7 100644
--- a/src/renderer/src/hooks/useAssistant.ts
+++ b/src/renderer/src/hooks/useAssistant.ts
@@ -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) => {
dispatch(updateAssistantSettings({ assistantId: assistant.id, settings }))
}
}
diff --git a/src/renderer/src/i18n/en-us.json b/src/renderer/src/i18n/en-us.json
index 7f65d0a1..bcae0787 100644
--- a/src/renderer/src/i18n/en-us.json
+++ b/src/renderer/src/i18n/en-us.json
@@ -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",
diff --git a/src/renderer/src/i18n/zh-cn.json b/src/renderer/src/i18n/zh-cn.json
index 784ab0d8..ddc173ae 100644
--- a/src/renderer/src/i18n/zh-cn.json
+++ b/src/renderer/src/i18n/zh-cn.json
@@ -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": "翻译",
diff --git a/src/renderer/src/i18n/zh-tw.json b/src/renderer/src/i18n/zh-tw.json
index f136458f..4886d21a 100644
--- a/src/renderer/src/i18n/zh-tw.json
+++ b/src/renderer/src/i18n/zh-tw.json
@@ -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": "翻譯",
diff --git a/src/renderer/src/pages/agents/AgentEditPage.tsx b/src/renderer/src/pages/agents/AgentEditPage.tsx
deleted file mode 100644
index 2e520832..00000000
--- a/src/renderer/src/pages/agents/AgentEditPage.tsx
+++ /dev/null
@@ -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(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 (
-
-
- {t('agents.edit.title')}
-
-
-
- } arrow placement="rightBottom">
- {emoji}}>{t('common.select')}
-
-
-
-
-
-
- {t('agents.add.prompt')}{' '}
- : }
- onClick={handleButtonClick}
- disabled={loading}
- />
- >
- }
- rules={[{ required: true }]}
- style={{ position: 'relative' }}>
-
-
-
-
- {t('common.save')}
-
- navigate(-1)}>
- {t('common.cancel')}
-
-
-
-
-
-
- )
-}
-
-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
diff --git a/src/renderer/src/pages/agents/Agents.tsx b/src/renderer/src/pages/agents/Agents.tsx
new file mode 100644
index 00000000..eb7de27a
--- /dev/null
+++ b/src/renderer/src/pages/agents/Agents.tsx
@@ -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 = ({ 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: ,
+ onClick: () => AssistantSettingsPopup.show({ assistant: agent })
+ },
+ {
+ label: t('agents.add.title'),
+ key: 'add',
+ icon: ,
+ onClick: () => createAssistantFromAgent(agent)
+ },
+ { type: 'divider' },
+ {
+ label: t('common.delete'),
+ key: 'delete',
+ icon: ,
+ danger: true,
+ onClick: () => {
+ window.modal.confirm({
+ centered: true,
+ content: t('agents.delete.popup.content'),
+ onOk: () => removeAgent(agent.id)
+ })
+ }
+ }
+ ] as ItemType[],
+ [removeAgent, t]
+ )
+
+ return (
+
+ {agents.length > 0 && (
+ setDragging(true)}
+ onDragEnd={() => setDragging(false)}>
+ {(agent: Agent) => (
+
+ onClick(agent)}>
+
+
+ {agent.emoji} {agent.name}
+
+ e.stopPropagation()}>
+
+
+
+
+
+ {agent.prompt}
+
+
+ )}
+
+ )}
+ {!dragging && (
+ }
+ onClick={() => AddAgentPopup.show()}
+ style={{ borderRadius: 20, height: 34 }}>
+ {t('agents.add.title')}
+
+ )}
+
+ )
+}
+
+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
diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx
index ecc1dd12..a2fa3dca 100644
--- a/src/renderer/src/pages/agents/AgentsPage.tsx
+++ b/src/renderer/src/pages/agents/AgentsPage.tsx
@@ -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: (
+
+ {agent.description || agent.prompt}
+
+ ),
+ 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 = () => {
{t('agents.title')}
-
+
{Object.keys(agentGroups)
@@ -65,7 +73,10 @@ const AppsPage: FC = () => {
{agentGroups[group].map((agent, index) => {
return (
- onAddAgentConfirm(agent)} agent={agent as any} />
+ onAddAgentConfirm(getAgentFromSystemAgent(agent))}
+ agent={agent as any}
+ />
)
})}
@@ -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
diff --git a/src/renderer/src/pages/agents/components/AddAgentPopup.tsx b/src/renderer/src/pages/agents/components/AddAgentPopup.tsx
index 3b4ca005..0d613a21 100644
--- a/src/renderer/src/pages/agents/components/AddAgentPopup.tsx
+++ b/src/renderer/src/pages/agents/components/AddAgentPopup.tsx
@@ -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 = ({ 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 = ({ 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 = ({ 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 = ({ resolve }) => {
onCancel={onCancel}
maskClosable={false}
afterClose={onClose}
- okText={t('agents.add.button')}
+ okText={t('agents.add.title')}
centered>