refactor(AgentSettings): restructure settings components for better reusability

- Replace SettingsInline with more flexible SettingsItem component
- Add SettingsContainer for consistent layout
- Remove redundant styled components in favor of shared components
This commit is contained in:
icarus 2025-09-21 22:41:09 +08:00
parent ea62294bd8
commit f49d3791b6
3 changed files with 112 additions and 85 deletions

View File

@ -5,8 +5,7 @@ import { Input } from 'antd'
import { FC, useState } from 'react' import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SettingDivider } from '..' import { AgentLabel, SettingsContainer, SettingsItem, SettingsTitle } from './shared'
import { AgentLabel, SettingsInline, SettingsTitle } from './shared'
interface AgentEssentialSettingsProps { interface AgentEssentialSettingsProps {
agent: AgentEntity | undefined | null agent: AgentEntity | undefined | null
@ -26,27 +25,28 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
if (!agent) return null if (!agent) return null
return ( return (
<div className="flex flex-1 flex-col overflow-hidden"> <SettingsContainer>
<SettingsInline> <SettingsItem inline>
<SettingsTitle>{t('agent.type.label')}</SettingsTitle> <SettingsTitle>{t('agent.type.label')}</SettingsTitle>
<AgentLabel type={agent.type} /> <AgentLabel type={agent.type} />
</SettingsInline> </SettingsItem>
<SettingDivider /> <SettingsItem>
<SettingsTitle>{t('common.name')}</SettingsTitle> <SettingsTitle>{t('common.name')}</SettingsTitle>
<HStack gap={8} alignItems="center"> <HStack gap={8} alignItems="center">
<Input <Input
placeholder={t('common.assistant') + t('common.name')} placeholder={t('common.assistant') + t('common.name')}
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
onBlur={() => { onBlur={() => {
if (name !== agent.name) { if (name !== agent.name) {
onUpdate() onUpdate()
} }
}} }}
style={{ flex: 1 }} style={{ flex: 1 }}
/> />
</HStack> </HStack>
</div> </SettingsItem>
</SettingsContainer>
) )
} }

View File

@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import styled from 'styled-components' import styled from 'styled-components'
import { SettingsTitle } from './shared' import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
interface AgentPromptSettingsProps { interface AgentPromptSettingsProps {
agent: AgentEntity | undefined | null agent: AgentEntity | undefined | null
@ -51,70 +51,65 @@ const AgentPromptSettings: FC<AgentPromptSettingsProps> = ({ agent, update }) =>
if (!agent) return null if (!agent) return null
return ( return (
<Container> <SettingsContainer>
<SettingsTitle> <SettingsItem divider={false} className="flex-1">
{t('common.prompt')} <SettingsTitle>
<Popover title={t('agents.add.prompt.variables.tip.title')} content={promptVarsContent}> {t('common.prompt')}
<HelpCircle size={14} color="var(--color-text-2)" /> <Popover title={t('agents.add.prompt.variables.tip.title')} content={promptVarsContent}>
</Popover> <HelpCircle size={14} color="var(--color-text-2)" />
</SettingsTitle> </Popover>
<TextAreaContainer> </SettingsTitle>
<RichEditorContainer> <TextAreaContainer>
{showPreview ? ( <RichEditorContainer>
<MarkdownContainer {showPreview ? (
onDoubleClick={() => { <MarkdownContainer
const currentScrollTop = editorRef.current?.getScrollTop?.() || 0 onDoubleClick={() => {
const currentScrollTop = editorRef.current?.getScrollTop?.() || 0
setShowPreview(false)
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
}}>
<ReactMarkdown>{processedPrompt || instructions}</ReactMarkdown>
</MarkdownContainer>
) : (
<CodeEditor
value={instructions}
language="markdown"
onChange={setInstructions}
height="100%"
expanded={false}
style={{
height: '100%'
}}
/>
)}
</RichEditorContainer>
</TextAreaContainer>
<HSpaceBetweenStack width="100%" justifyContent="flex-end" mt="10px">
<TokenCount>Tokens: {tokenCount}</TokenCount>
<Button
type="primary"
icon={showPreview ? <Edit size={14} /> : <Save size={14} />}
onClick={() => {
const currentScrollTop = editorRef.current?.getScrollTop?.() || 0
if (showPreview) {
setShowPreview(false) setShowPreview(false)
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop)) requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
}}> } else {
<ReactMarkdown>{processedPrompt || instructions}</ReactMarkdown> onUpdate()
</MarkdownContainer> requestAnimationFrame(() => {
) : ( setShowPreview(true)
<CodeEditor requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
value={instructions} })
language="markdown" }
onChange={setInstructions} }}>
height="100%" {showPreview ? t('common.edit') : t('common.save')}
expanded={false} </Button>
style={{ </HSpaceBetweenStack>
height: '100%' </SettingsItem>
}} </SettingsContainer>
/>
)}
</RichEditorContainer>
</TextAreaContainer>
<HSpaceBetweenStack width="100%" justifyContent="flex-end" mt="10px">
<TokenCount>Tokens: {tokenCount}</TokenCount>
<Button
type="primary"
icon={showPreview ? <Edit size={14} /> : <Save size={14} />}
onClick={() => {
const currentScrollTop = editorRef.current?.getScrollTop?.() || 0
if (showPreview) {
setShowPreview(false)
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
} else {
onUpdate()
requestAnimationFrame(() => {
setShowPreview(true)
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
})
}
}}>
{showPreview ? t('common.edit') : t('common.save')}
</Button>
</HSpaceBetweenStack>
</Container>
) )
} }
const Container = styled.div`
display: flex;
flex: 1;
flex-direction: column;
overflow: hidden;
`
const TextAreaContainer = styled.div` const TextAreaContainer = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;

View File

@ -4,14 +4,12 @@ import { getAgentTypeLabel } from '@renderer/i18n/label'
import { AgentType } from '@renderer/types' import { AgentType } from '@renderer/types'
import React from 'react' import React from 'react'
import { SettingDivider } from '..'
export const SettingsTitle: React.FC<React.PropsWithChildren> = ({ children }) => { export const SettingsTitle: React.FC<React.PropsWithChildren> = ({ children }) => {
return <div className="mb-1 flex items-center gap-2 font-bold">{children}</div> return <div className="mb-1 flex items-center gap-2 font-bold">{children}</div>
} }
export const SettingsInline: React.FC<React.PropsWithChildren> = ({ children }) => {
return <div className="flex items-center justify-between gap-2">{children}</div>
}
export type AgentLabelProps = { export type AgentLabelProps = {
type: AgentType type: AgentType
name?: string name?: string
@ -31,3 +29,37 @@ export const AgentLabel: React.FC<AgentLabelProps> = ({ type, name, classNames,
</div> </div>
) )
} }
export interface SettingsItemProps extends React.ComponentPropsWithRef<'div'> {
/** Add a divider beneath the item if true, defaults to true. */
divider?: boolean
/** Apply row direction flex or not, defaults to false. */
inline?: boolean
}
export const SettingsItem: React.FC<SettingsItemProps> = ({
children,
divider = true,
inline = false,
className,
...props
}) => {
return (
<>
<div
{...props}
className={cn('flex flex-col', inline ? 'flex-row items-center justify-between gap-2' : undefined, className)}>
{children}
</div>
{divider && <SettingDivider />}
</>
)
}
export const SettingsContainer: React.FC<React.ComponentPropsWithRef<'div'>> = ({ children, className, ...props }) => {
return (
<div className={cn('flex flex-1 flex-col overflow-hidden', className)} {...props}>
{children}
</div>
)
}