From bfe2e87f59577c99b31be6d2a28ef549730ad9be Mon Sep 17 00:00:00 2001 From: icarus Date: Sun, 21 Sep 2025 21:27:04 +0800 Subject: [PATCH] feat(agent-settings): add prompt settings tab and refactor essential settings - Introduce new AgentPromptSettings component for managing agent prompts - Move prompt-related functionality from AgentEssentialSettings to new component - Add avatar display to essential settings - Improve layout structure and styling for both settings components --- .../AgentSettings/AgentEssentialSettings.tsx | 146 +--------------- .../AgentSettings/AgentPromptSettings.tsx | 160 ++++++++++++++++++ .../pages/settings/AgentSettings/index.tsx | 12 +- 3 files changed, 180 insertions(+), 138 deletions(-) create mode 100644 src/renderer/src/pages/settings/AgentSettings/AgentPromptSettings.tsx diff --git a/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx index 621b84e424..90eb278295 100644 --- a/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx @@ -1,18 +1,11 @@ -import CodeEditor from '@renderer/components/CodeEditor' -import { Box, HSpaceBetweenStack, HStack } from '@renderer/components/Layout' -import { RichEditorRef } from '@renderer/components/RichEditor/types' +import { Avatar } from '@heroui/react' +import { Box, HStack } from '@renderer/components/Layout' +import { getAgentAvatar } from '@renderer/config/agent' import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' -import { usePromptProcessor } from '@renderer/hooks/usePromptProcessor' -import { estimateTextTokens } from '@renderer/services/TokenService' import { AgentEntity, UpdateAgentForm } from '@renderer/types' -import { Button, Input, Popover } from 'antd' -import { Edit, HelpCircle, Save } from 'lucide-react' -import { FC, useEffect, useRef, useState } from 'react' +import { Input } from 'antd' +import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' -import ReactMarkdown from 'react-markdown' -import styled from 'styled-components' - -import { SettingDivider } from '..' interface AgentEssentialSettingsProps { agent: AgentEntity | undefined | null @@ -22,41 +15,22 @@ interface AgentEssentialSettingsProps { const AgentEssentialSettings: FC = ({ agent, update }) => { const { t } = useTranslation() const [name, setName] = useState((agent?.name ?? '').trim()) - const [instructions, setInstructions] = useState(agent?.instructions ?? '') - const [showPreview, setShowPreview] = useState(!!agent?.instructions?.length) - const [tokenCount, setTokenCount] = useState(0) - - useEffect(() => { - const updateTokenCount = async () => { - const count = estimateTextTokens(instructions) - setTokenCount(count) - } - updateTokenCount() - }, [instructions]) - - const editorRef = useRef(null) - - const processedPrompt = usePromptProcessor({ - prompt: instructions, - modelName: agent?.model - }) const onUpdate = () => { if (!agent) return - const _agent = { ...agent, type: undefined, name: name.trim(), instructions } satisfies UpdateAgentForm + const _agent = { ...agent, type: undefined, name: name.trim() } satisfies UpdateAgentForm update(_agent) } - const promptVarsContent =
{t('agents.add.prompt.variables.tip.content')}
- if (!agent) return null return ( - +
{t('common.name')} + = ({ agent, update style={{ flex: 1 }} /> - - - {t('common.prompt')} - - - - - - - {showPreview ? ( - { - const currentScrollTop = editorRef.current?.getScrollTop?.() || 0 - setShowPreview(false) - requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop)) - }}> - {processedPrompt || instructions} - - ) : ( - - )} - - - - Tokens: {tokenCount} - - - +
) } -const Container = styled.div` - display: flex; - flex: 1; - flex-direction: column; - overflow: hidden; -` - -const TextAreaContainer = styled.div` - position: relative; - width: 100%; -` - -const TokenCount = styled.div` - padding: 2px 2px; - border-radius: 4px; - font-size: 14px; - color: var(--color-text-2); - user-select: none; -` - -const RichEditorContainer = styled.div` - height: calc(80vh - 202px); - border: 0.5px solid var(--color-border); - border-radius: 5px; - overflow: hidden; - - .prompt-rich-editor { - border: none; - height: 100%; - - .rich-editor-wrapper { - height: 100%; - display: flex; - flex-direction: column; - } - - .rich-editor-content { - flex: 1; - overflow: auto; - } - } -` - -const MarkdownContainer = styled.div.attrs({ className: 'markdown' })` - height: 100%; - padding: 0.5em; - overflow: auto; -` - export default AgentEssentialSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/AgentPromptSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/AgentPromptSettings.tsx new file mode 100644 index 0000000000..80f9931901 --- /dev/null +++ b/src/renderer/src/pages/settings/AgentSettings/AgentPromptSettings.tsx @@ -0,0 +1,160 @@ +import CodeEditor from '@renderer/components/CodeEditor' +import { Box, HSpaceBetweenStack, HStack } from '@renderer/components/Layout' +import { RichEditorRef } from '@renderer/components/RichEditor/types' +import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' +import { usePromptProcessor } from '@renderer/hooks/usePromptProcessor' +import { estimateTextTokens } from '@renderer/services/TokenService' +import { AgentEntity, UpdateAgentForm } from '@renderer/types' +import { Button, Popover } from 'antd' +import { Edit, HelpCircle, Save } from 'lucide-react' +import { FC, useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import ReactMarkdown from 'react-markdown' +import styled from 'styled-components' + +interface AgentPromptSettingsProps { + agent: AgentEntity | undefined | null + update: ReturnType +} + +const AgentPromptSettings: FC = ({ agent, update }) => { + const { t } = useTranslation() + const [instructions, setInstructions] = useState(agent?.instructions ?? '') + const [showPreview, setShowPreview] = useState(!!agent?.instructions?.length) + const [tokenCount, setTokenCount] = useState(0) + + useEffect(() => { + const updateTokenCount = async () => { + const count = estimateTextTokens(instructions) + setTokenCount(count) + } + updateTokenCount() + }, [instructions]) + + const editorRef = useRef(null) + + const processedPrompt = usePromptProcessor({ + prompt: instructions, + modelName: agent?.model + }) + + const onUpdate = () => { + if (!agent) return + const _agent = { ...agent, type: undefined, instructions } satisfies UpdateAgentForm + update(_agent) + } + + const promptVarsContent =
{t('agents.add.prompt.variables.tip.content')}
+ + if (!agent) return null + + return ( + + + {t('common.prompt')} + + + + + + + {showPreview ? ( + { + const currentScrollTop = editorRef.current?.getScrollTop?.() || 0 + setShowPreview(false) + requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop)) + }}> + {processedPrompt || instructions} + + ) : ( + + )} + + + + Tokens: {tokenCount} + + + + ) +} + +const Container = styled.div` + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; +` + +const TextAreaContainer = styled.div` + position: relative; + width: 100%; + flex: 1; +` + +const TokenCount = styled.div` + padding: 2px 2px; + border-radius: 4px; + font-size: 14px; + color: var(--color-text-2); + user-select: none; +` + +const RichEditorContainer = styled.div` + height: 100%; + flex: 1; + border: 0.5px solid var(--color-border); + border-radius: 5px; + overflow: hidden; + + .prompt-rich-editor { + border: none; + height: 100%; + + .rich-editor-wrapper { + height: 100%; + display: flex; + flex-direction: column; + } + + .rich-editor-content { + flex: 1; + overflow: auto; + } + } +` + +const MarkdownContainer = styled.div.attrs({ className: 'markdown' })` + height: 100%; + padding: 0.5em; + overflow: auto; +` + +export default AgentPromptSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/index.tsx b/src/renderer/src/pages/settings/AgentSettings/index.tsx index 9de26a617b..9a2cbc39c8 100644 --- a/src/renderer/src/pages/settings/AgentSettings/index.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/index.tsx @@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import AgentEssentialSettings from './AgentEssentialSettings' +import AgentPromptSettings from './AgentPromptSettings' interface AgentSettingPopupShowParams { agentId: string @@ -47,6 +48,10 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag { key: 'essential', label: t('agent.settings.essential') + }, + { + key: 'prompt', + label: t('agent.settings.prompt') } ] satisfies { key: AgentSettingPopupTab; label: string }[] ).filter(Boolean) as { key: string; label: string }[] @@ -88,7 +93,10 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag onSelect={({ key }) => setMenu(key as AgentSettingPopupTab)} /> - {menu === 'essential' && } + + {menu === 'essential' && } + {menu === 'prompt' && } + ) @@ -100,6 +108,8 @@ const LeftMenu = styled.div` ` const Settings = styled.div` + display: flex; + flex-direction: column; flex: 1; padding: 16px 16px; height: calc(80vh - 16px);