feat: improvedscrollbar component functionality and added internationalization support to agentspage component

This commit is contained in:
kangfenmao 2024-10-31 11:31:38 +08:00
parent b80270709f
commit 63e5972dd2
6 changed files with 248 additions and 45 deletions

View File

@ -1,49 +1,57 @@
import { forwardRef } from 'react'
import { throttle } from 'lodash'
import { FC, forwardRef, useCallback, useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
interface Props {
children?: React.ReactNode
className?: string
$isScrolling?: boolean
$right?: boolean
interface Props extends React.HTMLAttributes<HTMLDivElement> {
right?: boolean
ref?: any
}
const ScrollbarContainer = styled.div<{ $isScrolling?: boolean; $right?: boolean }>`
overflow-y: auto;
overflow-x: hidden;
height: 100%;
const Scrollbar: FC<Props> = forwardRef<HTMLDivElement, Props>((props, ref) => {
const [isScrolling, setIsScrolling] = useState(false)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
&::-webkit-scrollbar {
width: 6px;
height: 6px;
const handleScroll = useCallback(
throttle(() => {
setIsScrolling(true)
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
&::-webkit-scrollbar-track {
border-radius: 3px;
background: transparent;
${({ $right }) => $right && `margin-right: 4px;`}
}
timeoutRef.current = setTimeout(() => setIsScrolling(false), 1500) // 增加到 2 秒
}, 200),
[]
)
&::-webkit-scrollbar-thumb {
border-radius: 3px;
background: ${({ $isScrolling }) =>
$isScrolling ? 'var(--color-scrollbar-thumb)' : 'var(--color-scrollbar-track)'};
transition: all 0.2s ease-in-out;
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
&:hover::-webkit-scrollbar-thumb {
background: var(--color-scrollbar-thumb);
}
`
}, [])
const Scrollbar = forwardRef<HTMLDivElement, Props>(({ children, className, $isScrolling, $right }, ref) => {
return (
<ScrollbarContainer ref={ref} className={className} $isScrolling={$isScrolling} $right={$right}>
{children}
</ScrollbarContainer>
<Container {...props} isScrolling={isScrolling} onScroll={handleScroll} ref={ref}>
{props.children}
</Container>
)
})
const Container = styled.div<{ isScrolling: boolean; right?: boolean }>`
overflow-y: auto;
&::-webkit-scrollbar-thumb {
transition: background 2s ease;
background: ${(props) =>
props.isScrolling ? `var(--color-scrollbar-thumb${props.right ? '-right' : ''})` : 'transparent'};
&:hover {
background: ${(props) =>
props.isScrolling ? `var(--color-scrollbar-thumb${props.right ? '-right' : ''}-hover)` : 'transparent'};
}
}
`
Scrollbar.displayName = 'Scrollbar'
export default Scrollbar

View File

@ -75,7 +75,7 @@ const Agents: React.FC<Props> = ({ onClick, cardStyle = 'old' }) => {
]
return (
<Col span={8} key={agent.id}>
<Col span={8} xxl={6} key={agent.id}>
<AgentCard
agent={agent}
onClick={() => onClick?.(agent)}

View File

@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next'
import ReactMarkdown from 'react-markdown'
import styled from 'styled-components'
import { groupTranslations } from './agentGroupTranslations'
import Agents from './Agents'
import AddAgentPopup from './components/AddAgentPopup'
import AgentCard from './components/AgentCard'
@ -41,7 +42,7 @@ const AgentsPage: FC = () => {
return _agentGroups
}, [])
const { t } = useTranslation()
const { t, i18n } = useTranslation()
const filteredAgentGroups = useMemo(() => {
const groups = search.trim() ? {} : { : [] }
@ -102,6 +103,14 @@ const AgentsPage: FC = () => {
}
}
const getLocalizedGroupName = useCallback(
(group: string) => {
const currentLang = i18n.language
return groupTranslations[group]?.[currentLang] || group
},
[i18n.language]
)
const tabItems = useMemo(() => {
let groups = Object.keys(filteredAgentGroups)
groups = groups.filter((g) => g !== '我的' && g !== '办公')
@ -109,25 +118,27 @@ const AgentsPage: FC = () => {
return groups.map((group, i) => {
const id = String(i + 1)
const localizedGroupName = getLocalizedGroupName(group)
return {
label: group,
label: localizedGroupName,
key: id,
children: (
<TabContent key={group}>
<Title level={5} key={group} style={{ marginBottom: 16 }}>
{group}
{localizedGroupName}
</Title>
<Row gutter={[32, 32]}>
<Row gutter={[25, 25]}>
{group === '我的' ? (
<>
<Col span={8}>
<Col span={8} xxl={6}>
<AddAgentCard onClick={() => AddAgentPopup.show()} />
</Col>
<Agents onClick={onAddAgentConfirm} cardStyle="new" />
</>
) : (
filteredAgentGroups[group]?.map((agent, index) => (
<Col span={8} key={group + index}>
<Col span={8} xxl={6} key={group + index}>
<AgentCard onClick={() => onAddAgentConfirm(getAgentFromSystemAgent(agent))} agent={agent as any} />
</Col>
))
@ -137,7 +148,7 @@ const AgentsPage: FC = () => {
)
}
})
}, [filteredAgentGroups, onAddAgentConfirm])
}, [filteredAgentGroups, getLocalizedGroupName, onAddAgentConfirm])
return (
<StyledContainer>
@ -187,6 +198,7 @@ const ContentContainer = styled.div`
flex-direction: row;
justify-content: center;
height: 100%;
padding: 0 10px;
`
const AssistantsContainer = styled.div`
@ -200,6 +212,7 @@ const TabContent = styled(Scrollbar)`
height: calc(100vh - var(--navbar-height));
padding: 10px 10px 10px 15px;
margin-right: 4px;
overflow-x: hidden;
`
const AgentPrompt = styled.div`
@ -215,7 +228,6 @@ const EmptyView = styled.div`
align-items: center;
font-size: 16px;
color: var(--color-text-secondary);
border-left: 0.5px solid var(--color-border);
`
const Tabs = styled(TabsAntd)`
@ -247,7 +259,7 @@ const Tabs = styled(TabsAntd)`
border-right: none;
}
.ant-tabs-content-holder {
border-left: 0.5px solid var(--color-border);
border-left: none;
border-right: 0.5px solid var(--color-border);
}
.ant-tabs-ink-bar {

View File

@ -0,0 +1,180 @@
export type GroupTranslations = {
[key: string]: {
'en-US': string
'zh-CN': string
'zh-TW': string
}
}
export const groupTranslations: GroupTranslations = {
: {
'en-US': 'My Agents',
'zh-CN': '我的',
'zh-TW': '我的'
},
: {
'en-US': 'Career',
'zh-CN': '职业',
'zh-TW': '職業'
},
: {
'en-US': 'Business',
'zh-CN': '商业',
'zh-TW': '商業'
},
: {
'en-US': 'Tools',
'zh-CN': '工具',
'zh-TW': '工具'
},
: {
'en-US': 'Language',
'zh-CN': '语言',
'zh-TW': '語言'
},
: {
'en-US': 'Office',
'zh-CN': '办公',
'zh-TW': '辦公'
},
: {
'en-US': 'General',
'zh-CN': '通用',
'zh-TW': '通用'
},
: {
'en-US': 'Writing',
'zh-CN': '写作',
'zh-TW': '寫作'
},
Artifacts: {
'en-US': 'Artifacts',
'zh-CN': 'Artifacts',
'zh-TW': 'Artifacts'
},
: {
'en-US': 'Programming',
'zh-CN': '编程',
'zh-TW': '編程'
},
: {
'en-US': 'Emotion',
'zh-CN': '情感',
'zh-TW': '情感'
},
: {
'en-US': 'Education',
'zh-CN': '教育',
'zh-TW': '教育'
},
: {
'en-US': 'Creative',
'zh-CN': '创意',
'zh-TW': '創意'
},
: {
'en-US': 'Academic',
'zh-CN': '学术',
'zh-TW': '學術'
},
: {
'en-US': 'Design',
'zh-CN': '设计',
'zh-TW': '設計'
},
: {
'en-US': 'Art',
'zh-CN': '艺术',
'zh-TW': '藝術'
},
: {
'en-US': 'Entertainment',
'zh-CN': '娱乐',
'zh-TW': '娛樂'
},
: {
'en-US': 'Life',
'zh-CN': '生活',
'zh-TW': '生活'
},
: {
'en-US': 'Medical',
'zh-CN': '医疗',
'zh-TW': '醫療'
},
: {
'en-US': 'Games',
'zh-CN': '游戏',
'zh-TW': '遊戲'
},
: {
'en-US': 'Translation',
'zh-CN': '翻译',
'zh-TW': '翻譯'
},
: {
'en-US': 'Music',
'zh-CN': '音乐',
'zh-TW': '音樂'
},
: {
'en-US': 'Review',
'zh-CN': '点评',
'zh-TW': '點評'
},
: {
'en-US': 'Copywriting',
'zh-CN': '文案',
'zh-TW': '文案'
},
: {
'en-US': 'Encyclopedia',
'zh-CN': '百科',
'zh-TW': '百科'
},
: {
'en-US': 'Health',
'zh-CN': '健康',
'zh-TW': '健康'
},
: {
'en-US': 'Marketing',
'zh-CN': '营销',
'zh-TW': '營銷'
},
: {
'en-US': 'Science',
'zh-CN': '科学',
'zh-TW': '科學'
},
: {
'en-US': 'Analysis',
'zh-CN': '分析',
'zh-TW': '分析'
},
: {
'en-US': 'Legal',
'zh-CN': '法律',
'zh-TW': '法律'
},
: {
'en-US': 'Consulting',
'zh-CN': '咨询',
'zh-TW': '諮詢'
},
: {
'en-US': 'Finance',
'zh-CN': '金融',
'zh-TW': '金融'
},
: {
'en-US': 'Travel',
'zh-CN': '旅游',
'zh-TW': '旅遊'
},
: {
'en-US': 'Management',
'zh-CN': '管理',
'zh-TW': '管理'
}
}

View File

@ -1,5 +1,6 @@
import { EllipsisOutlined } from '@ant-design/icons'
import { Agent } from '@renderer/types'
import { getLeadingEmoji } from '@renderer/utils'
import { Dropdown } from 'antd'
import styled from 'styled-components'
@ -171,10 +172,11 @@ const MenuContainer = styled.div`
`
const AgentCard: React.FC<Props> = ({ agent, onClick, contextMenu, menuItems }) => {
const emoji = agent.emoji || getLeadingEmoji(agent.name)
const content = (
<Container onClick={onClick}>
{agent.emoji && <BannerBackground className="banner-background">{agent.emoji}</BannerBackground>}
<EmojiContainer className="emoji-container">{agent.emoji}</EmojiContainer>
<EmojiContainer className="emoji-container">{emoji}</EmojiContainer>
{menuItems && (
<MenuContainer onClick={(e) => e.stopPropagation()}>
<Dropdown
@ -198,7 +200,7 @@ const AgentCard: React.FC<Props> = ({ agent, onClick, contextMenu, menuItems })
)}
<CardInfo className="card-info">
<AgentName>{agent.name}</AgentName>
<AgentPrompt className="agent-prompt">{(agent.description || agent.prompt).substring(0, 50)}...</AgentPrompt>
<AgentPrompt className="agent-prompt">{(agent.description || agent.prompt).substring(0, 100)}...</AgentPrompt>
</CardInfo>
</Container>
)

View File

@ -153,6 +153,7 @@ const ImageWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
border: 0.5px solid var(--color-border);
.ant-image {
height: 100%;