diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 030473a2b6..c0a4cc0c41 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -9,6 +9,7 @@ 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' @@ -30,6 +31,7 @@ function App(): JSX.Element { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index 4c12316b88..27b0227701 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -54,7 +54,7 @@ const Sidebar: FC = () => { to('/agents')}> - + diff --git a/src/renderer/src/hooks/useAgents.ts b/src/renderer/src/hooks/useAgents.ts index 2e91c846c6..87a4753b4f 100644 --- a/src/renderer/src/hooks/useAgents.ts +++ b/src/renderer/src/hooks/useAgents.ts @@ -15,3 +15,14 @@ export function useAgents() { updateAgents: (agents: Agent[]) => dispatch(updateAgents(agents)) } } + +export function useAgent(id: string) { + const agents = useSelector((state: RootState) => state.agents.agents) + const dispatch = useDispatch() + const agent = agents.find((a) => a.id === id) + + return { + agent, + updateAgent: (agent: Agent) => dispatch(updateAgent(agent)) + } +} diff --git a/src/renderer/src/i18n/en-us.json b/src/renderer/src/i18n/en-us.json index 77c1159b61..7f65d0a1d3 100644 --- a/src/renderer/src/i18n/en-us.json +++ b/src/renderer/src/i18n/en-us.json @@ -28,7 +28,8 @@ "warning": "Warning", "back": "Back", "chat": "Chat", - "close": "Close" + "close": "Close", + "cancel": "Cancel" }, "button": { "add": "Add", diff --git a/src/renderer/src/i18n/zh-cn.json b/src/renderer/src/i18n/zh-cn.json index 26507c6aab..784ab0d8f9 100644 --- a/src/renderer/src/i18n/zh-cn.json +++ b/src/renderer/src/i18n/zh-cn.json @@ -28,7 +28,8 @@ "warning": "警告", "back": "返回", "chat": "聊天", - "close": "关闭" + "close": "关闭", + "cancel": "取消" }, "button": { "add": "添加", diff --git a/src/renderer/src/i18n/zh-tw.json b/src/renderer/src/i18n/zh-tw.json index 1a332115e6..f136458fbf 100644 --- a/src/renderer/src/i18n/zh-tw.json +++ b/src/renderer/src/i18n/zh-tw.json @@ -28,7 +28,8 @@ "warning": "警告", "back": "返回", "chat": "聊天", - "close": "關閉" + "close": "關閉", + "cancel": "取消" }, "button": { "add": "添加", diff --git a/src/renderer/src/pages/agents/AgentEditPage.tsx b/src/renderer/src/pages/agents/AgentEditPage.tsx new file mode 100644 index 0000000000..1585652339 --- /dev/null +++ b/src/renderer/src/pages/agents/AgentEditPage.tsx @@ -0,0 +1,157 @@ +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')} + + + + + + + + + + : } + onClick={handleButtonClick} + style={{ position: 'absolute', top: 8, right: 8 }} + disabled={loading} + /> + + + + {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/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index 7970f92232..ecc1dd1209 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -1,8 +1,6 @@ -import { UnorderedListOutlined } from '@ant-design/icons' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' -import { HStack } from '@renderer/components/Layout' +import { VStack } from '@renderer/components/Layout' import Agents from '@renderer/config/agents.json' -import { useAgents } from '@renderer/hooks/useAgents' import { useAssistants } from '@renderer/hooks/useAssistant' import { covertAgentToAssistant } from '@renderer/services/assistant' import { Agent } from '@renderer/types' @@ -13,14 +11,12 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import AgentCard from './components/AgentCard' -import ManageAgentsPopup from './components/ManageAgentsPopup' -import UserAgents from './components/UserAgents' +import MyAgents from './components/MyAgents' const { Title } = Typography const AppsPage: FC = () => { const { assistants, addAssistant } = useAssistants() - const { agents } = useAgents() const agentGroups = groupBy(Agents, 'group') const { t } = useTranslation() @@ -55,31 +51,29 @@ const AppsPage: FC = () => { {t('agents.title')} + - - {t('agents.my_agents')} - {agents.length > 0 && } - - - {Object.keys(agentGroups) - .reverse() - .map((group) => ( - - - {group} - - - {agentGroups[group].map((agent, index) => { - return ( - - onAddAgentConfirm(agent)} agent={agent as any} /> - - ) - })} - - - ))} - + + {Object.keys(agentGroups) + .reverse() + .map((group) => ( + + + {group} + + + {agentGroups[group].map((agent, index) => { + return ( + + onAddAgentConfirm(agent)} agent={agent as any} /> + + ) + })} + + + ))} + + @@ -99,24 +93,15 @@ const ContentContainer = styled.div` flex-direction: row; justify-content: center; height: 100%; - overflow-y: scroll; ` const AssistantsContainer = styled.div` display: flex; flex: 1; - flex-direction: column; + flex-direction: row; height: calc(100vh - var(--navbar-height)); - padding: 20px; - max-width: 1000px; -` - -const ManageIcon = styled(UnorderedListOutlined)` - font-size: 18px; - color: var(--color-icon); - cursor: pointer; - margin-bottom: 0.5em; - margin-left: 0.5em; + padding: 15px 20px; + overflow-y: scroll; ` export default AppsPage diff --git a/src/renderer/src/pages/agents/components/AddAgentPopup.tsx b/src/renderer/src/pages/agents/components/AddAgentPopup.tsx index 3e4784579f..3b4ca00505 100644 --- a/src/renderer/src/pages/agents/components/AddAgentPopup.tsx +++ b/src/renderer/src/pages/agents/components/AddAgentPopup.tsx @@ -5,16 +5,14 @@ import EmojiPicker from '@renderer/components/EmojiPicker' import { TopView } from '@renderer/components/TopView' import { useAgents } from '@renderer/hooks/useAgents' import { fetchGenerate } from '@renderer/services/api' -import { syncAgentToAssistant } from '@renderer/services/assistant' import { Agent } from '@renderer/types' import { getLeadingEmoji, uuid } from '@renderer/utils' import { Button, Form, FormInstance, Input, Modal, Popover } from 'antd' import TextArea from 'antd/es/input/TextArea' -import { useEffect, useRef, useState } from 'react' +import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' interface Props { - agent?: Agent resolve: (data: Agent | null) => void } @@ -24,13 +22,13 @@ type FieldType = { prompt: string } -const PopupContainer: React.FC = ({ agent, resolve }) => { +const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const [form] = Form.useForm() const { t } = useTranslation() - const { addAgent, updateAgent } = useAgents() + const { addAgent } = useAgents() const formRef = useRef(null) - const [emoji, setEmoji] = useState(agent?.emoji) + const [emoji, setEmoji] = useState('') const [loading, setLoading] = useState(false) const onFinish = (values: FieldType) => { @@ -40,20 +38,6 @@ const PopupContainer: React.FC = ({ agent, resolve }) => { return } - if (agent) { - const _agent = { - ...agent, - name: values.name, - emoji: _emoji, - prompt: values.prompt - } - updateAgent(_agent) - syncAgentToAssistant(_agent) - resolve(_agent) - setOpen(false) - return - } - const _agent = { id: uuid(), name: values.name, @@ -75,15 +59,6 @@ const PopupContainer: React.FC = ({ agent, resolve }) => { resolve(null) } - useEffect(() => { - if (agent) { - form.setFieldsValue({ - name: agent.name, - prompt: agent.prompt - }) - } - }, [agent, form]) - const handleButtonClick = async () => { const prompt = `你是一个专业的 prompt 优化助手,我会给你一段prompt,你需要帮我优化它,仅回复优化后的 prompt 不要添加任何解释,使用 [CRISPE提示框架] 回复。` @@ -114,13 +89,13 @@ const PopupContainer: React.FC = ({ agent, resolve }) => { return ( formRef.current?.submit()} onCancel={onCancel} maskClosable={false} afterClose={onClose} - okText={agent ? t('common.save') : t('agents.add.button')} + okText={t('agents.add.button')} centered> ((resolve) => { TopView.show( { resolve(v) this.hide() diff --git a/src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx b/src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx deleted file mode 100644 index f00eb36efb..0000000000 --- a/src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { DeleteOutlined, EditOutlined, MenuOutlined } from '@ant-design/icons' -import DragableList from '@renderer/components/DragableList' -import { Box, HStack } from '@renderer/components/Layout' -import { TopView } from '@renderer/components/TopView' -import { useAgents } from '@renderer/hooks/useAgents' -import { Empty, Modal, Popconfirm } from 'antd' -import { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import styled from 'styled-components' - -import AddAgentPopup from './AddAgentPopup' - -const PopupContainer: React.FC = () => { - const [open, setOpen] = useState(true) - const { t } = useTranslation() - const { agents, removeAgent, updateAgents } = useAgents() - - const onOk = () => { - setOpen(false) - } - - const onCancel = () => { - setOpen(false) - } - - const onClose = async () => { - ManageAgentsPopup.hide() - } - - useEffect(() => { - if (agents.length === 0) { - setOpen(false) - } - }, [agents]) - - return ( - - - {agents.length > 0 && ( - - {(item) => ( - - - {item.emoji} {item.name} - - - removeAgent(item)}> - - - AddAgentPopup.show(item)} /> - - - - )} - - )} - {agents.length === 0 && } - - - ) -} - -const Container = styled.div` - padding: 12px 0; - height: 50vh; - overflow-y: auto; - &::-webkit-scrollbar { - display: none; - } -` - -const AgentItem = styled.div` - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - padding: 8px; - border-radius: 8px; - 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 class ManageAgentsPopup { - static topviewId = 0 - static hide() { - TopView.hide('ManageAgentsPopup') - } - static show() { - TopView.show(, 'ManageAgentsPopup') - } -} diff --git a/src/renderer/src/pages/agents/components/MyAgents.tsx b/src/renderer/src/pages/agents/components/MyAgents.tsx new file mode 100644 index 0000000000..52071f7631 --- /dev/null +++ b/src/renderer/src/pages/agents/components/MyAgents.tsx @@ -0,0 +1,98 @@ +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 = ({ onClick }) => { + const { t } = useTranslation() + const { agents, removeAgent, updateAgents } = useAgents() + const [dragging, setDragging] = useState(false) + const navigate = useNavigate() + + return ( + + + {t('agents.my_agents')} + + {agents.length > 0 && ( + setDragging(true)} + onDragEnd={() => setDragging(false)}> + {(agent) => ( + onClick(agent)}> + + {agent.emoji} {agent.name} + + e.stopPropagation()}> + removeAgent(agent)}> + + + navigate(`/agents/${agent.id}`)} /> + + + )} + + )} + {!dragging && ( + } + onClick={() => AddAgentPopup.show()} + style={{ borderRadius: 20, height: 34 }}> + {t('agents.add.title')} + + )} + + ) +} + +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 diff --git a/src/renderer/src/pages/agents/components/UserAgents.tsx b/src/renderer/src/pages/agents/components/UserAgents.tsx deleted file mode 100644 index 2eb738ce1c..0000000000 --- a/src/renderer/src/pages/agents/components/UserAgents.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { PlusOutlined } from '@ant-design/icons' -import { useAgents } from '@renderer/hooks/useAgents' -import { Agent } from '@renderer/types' -import { Col, Row } from 'antd' -import { FC } from 'react' -import styled from 'styled-components' - -import AddAssistantPopup from './AddAgentPopup' -import AgentCard from './AgentCard' - -interface Props { - onAdd: (agent: Agent) => void -} - -const UserAgents: FC = ({ onAdd }) => { - const { agents } = useAgents() - - const onAddMyAgentClick = () => { - AddAssistantPopup.show() - } - - return ( - - {agents.map((agent) => ( - - onAdd(agent)} /> - - ))} - - - - - - - ) -} - -const AssistantCardContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 20px; - border: 1px dashed var(--color-border-soft); - border-radius: 10px; - cursor: pointer; - min-height: 72px; - .anticon { - font-size: 16px; - color: var(--color-icon); - } - &:hover { - background-color: var(--color-background-soft); - } -` - -export default UserAgents