feat: add agents tabs and search

This commit is contained in:
kangfenmao 2024-10-26 22:33:47 +08:00
parent 9ec0836d26
commit ac21c90b6f
5 changed files with 6033 additions and 130 deletions

View File

@ -20,6 +20,8 @@ db.all('SELECT * FROM agents', [], (err, rows) => {
// 将 ID 类型转换为字符串 // 将 ID 类型转换为字符串
for (const row of rows) { for (const row of rows) {
row.id = row.id.toString() row.id = row.id.toString()
row.group = row.group.toString().split(',')
row.group = row.group.map((item) => item.trim().replace('\r\n', ''))
} }
// 将查询结果转换为JSON字符串 // 将查询结果转换为JSON字符串

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
import { useAgents } from '@renderer/hooks/useAgents' import { useAgents } from '@renderer/hooks/useAgents'
import { createAssistantFromAgent } from '@renderer/services/assistant' import { createAssistantFromAgent } from '@renderer/services/assistant'
import { Agent } from '@renderer/types' import { Agent } from '@renderer/types'
import { Button, Dropdown } from 'antd' import { Button, Dropdown, Typography } from 'antd'
import { ItemType } from 'antd/es/menu/interface' import { ItemType } from 'antd/es/menu/interface'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -58,6 +58,9 @@ const Agents: React.FC<Props> = ({ onClick }) => {
return ( return (
<Container style={{ paddingBottom: dragging ? 30 : 0 }}> <Container style={{ paddingBottom: dragging ? 30 : 0 }}>
<Typography.Title level={5} style={{ marginBottom: 16 }}>
{t('agents.my_agents')}
</Typography.Title>
{agents.length > 0 && ( {agents.length > 0 && (
<DragableList <DragableList
list={agents} list={agents}
@ -98,23 +101,23 @@ const Agents: React.FC<Props> = ({ onClick }) => {
} }
const Container = styled(Scrollbar)` const Container = styled(Scrollbar)`
padding: 15px; padding: 10px 15px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-right: 0.5px solid var(--color-border);
min-height: calc(100vh - var(--navbar-height)); min-height: calc(100vh - var(--navbar-height));
width: var(--assistants-width); min-width: var(--assistants-width);
max-width: var(--assistants-width);
` `
const AgentItem = styled.div` const AgentItem = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0 12px; padding: 0 12px;
min-height: 38px; min-height: 72px;
border-radius: 10px; border-radius: 10px;
user-select: none; user-select: none;
margin-bottom: 12px; margin-bottom: 15px;
padding-bottom: 12px; padding-bottom: 10px;
border: 0.5px solid var(--color-border); border: 0.5px solid var(--color-border);
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
cursor: pointer; cursor: pointer;
@ -138,11 +141,16 @@ const AgentItemName = styled.div`
const AgentItemPrompt = styled.div` const AgentItemPrompt = styled.div`
font-size: 12px; font-size: 12px;
color: var(--color-text-soft); color: var(--color-text-soft);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: -5px; margin-top: -5px;
color: var(--color-text-3); color: var(--color-text-3);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
word-wrap: break-word;
line-height: 16px;
` `
const ActionButton = styled(HStack)` const ActionButton = styled(HStack)`

View File

@ -1,13 +1,13 @@
import { SearchOutlined } from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { VStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import SystemAgents from '@renderer/config/agents.json' import SystemAgents from '@renderer/config/agents.json'
import { createAssistantFromAgent } from '@renderer/services/assistant' import { createAssistantFromAgent } from '@renderer/services/assistant'
import { Agent } from '@renderer/types' import { Agent } from '@renderer/types'
import { uuid } from '@renderer/utils' import { uuid } from '@renderer/utils'
import { Col, Row, Typography } from 'antd' import { Col, Input, Row, Tabs as TabsAntd, Typography } from 'antd'
import { groupBy, omit } from 'lodash' import { debounce, groupBy, omit } from 'lodash'
import { FC } from 'react' import { FC, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' 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'
@ -17,10 +17,47 @@ import AgentCard from './components/AgentCard'
const { Title } = Typography const { Title } = Typography
const getAgentsFromSystemAgents = () => {
const agents: Agent[] = []
for (let i = 0; i < SystemAgents.length; i++) {
for (let j = 0; j < SystemAgents[i].group.length; j++) {
const agent = { ...SystemAgents[i], group: SystemAgents[i].group[j], topics: [], type: 'agent' } as Agent
agents.push(agent)
}
}
return agents
}
const AgentsPage: FC = () => { const AgentsPage: FC = () => {
const agentGroups = groupBy(SystemAgents, 'group') const [search, setSearch] = useState('')
const [filteredAgentGroups, setFilteredAgentGroups] = useState({})
const agentGroups = useMemo(() => groupBy(getAgentsFromSystemAgents(), 'group'), [])
const { t } = useTranslation() const { t } = useTranslation()
const debouncedSearch = useMemo(
() =>
debounce((searchTerm: string) => {
if (!searchTerm.trim()) {
setFilteredAgentGroups(agentGroups)
return
}
const filtered = {}
Object.entries(agentGroups).forEach(([group, agents]) => {
const filteredAgents = agents.filter(
(agent) =>
agent.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
agent.prompt?.toLowerCase().includes(searchTerm.toLowerCase())
)
if (filteredAgents.length > 0) {
filtered[group] = filteredAgents
}
})
setFilteredAgentGroups(filtered)
}, 500),
[agentGroups]
)
const getAgentName = (agent: Agent) => { const getAgentName = (agent: Agent) => {
return agent.emoji ? agent.emoji + ' ' + agent.name : agent.name return agent.emoji ? agent.emoji + ' ' + agent.name : agent.name
} }
@ -54,38 +91,62 @@ const AgentsPage: FC = () => {
} }
} }
useEffect(() => {
debouncedSearch(search)
}, [search, debouncedSearch])
return ( return (
<Container> <Container>
<Navbar> <Navbar>
<NavbarCenter style={{ borderRight: 'none' }}>{t('agents.title')}</NavbarCenter> <NavbarCenter style={{ borderRight: 'none', justifyContent: 'space-between' }}>
{t('agents.title')}
<Input
placeholder={t('common.search')}
className="nodrag"
style={{ width: '30%', height: 28 }}
size="small"
variant="filled"
suffix={<SearchOutlined />}
value={search}
maxLength={50}
onChange={(e) => setSearch(e.target.value)}
/>
<div style={{ width: 80 }} />
</NavbarCenter>
</Navbar> </Navbar>
<ContentContainer id="content-container"> <ContentContainer id="content-container">
<Agents onClick={onAddAgentConfirm} /> <AssistantsContainer>
<AssistantsContainer right> <Agents onClick={onAddAgentConfirm} />
<VStack style={{ flex: 1 }}> <Tabs
{Object.keys(agentGroups) tabPosition="left"
.reverse() animated
.map((group) => ( items={Object.keys(filteredAgentGroups).map((group, i) => {
<div key={group}> const id = String(i + 1)
<Title level={5} key={group} style={{ marginBottom: 16 }}> return {
{group} label: group,
</Title> key: id,
<Row gutter={16}> children: (
{agentGroups[group].map((agent, index) => { <TabContent key={group}>
return ( <Title level={5} key={group} style={{ marginBottom: 16 }}>
<Col span={8} key={group + index}> {group}
<AgentCard </Title>
onClick={() => onAddAgentConfirm(getAgentFromSystemAgent(agent))} <Row gutter={16}>
agent={agent as any} {filteredAgentGroups[group].map((agent, index) => {
/> return (
</Col> <Col span={8} key={group + index}>
) <AgentCard
})} onClick={() => onAddAgentConfirm(getAgentFromSystemAgent(agent))}
</Row> agent={agent as any}
</div> />
))} </Col>
<div style={{ minHeight: 20 }} /> )
</VStack> })}
</Row>
</TabContent>
)
}
})}
/>
</AssistantsContainer> </AssistantsContainer>
</ContentContainer> </ContentContainer>
</Container> </Container>
@ -107,12 +168,16 @@ const ContentContainer = styled.div`
height: 100%; height: 100%;
` `
const AssistantsContainer = styled(Scrollbar)` const AssistantsContainer = styled.div`
display: flex; display: flex;
flex: 1; flex: 1;
flex-direction: row; flex-direction: row;
height: calc(100vh - var(--navbar-height)); height: calc(100vh - var(--navbar-height));
padding: 15px 20px; `
const TabContent = styled(Scrollbar)`
height: calc(100vh - var(--navbar-height));
padding: 10px 10px 10px 15px;
margin-right: 4px; margin-right: 4px;
` `
@ -122,4 +187,39 @@ const AgentPrompt = styled.div`
max-width: 560px; max-width: 560px;
` `
const Tabs = styled(TabsAntd)`
display: flex;
flex: 1;
flex-direction: row-reverse;
.ant-tabs-tabpane {
padding-left: 0 !important;
}
.ant-tabs-nav-list {
padding: 10px;
}
.ant-tabs-nav-operations {
display: none !important;
}
.ant-tabs-tab {
margin: 0 !important;
border-radius: 6px;
margin-bottom: 5px !important;
font-size: 14px;
&:hover {
background-color: var(--color-background-soft);
}
}
.ant-tabs-tab-active {
background-color: var(--color-background-mute);
border-right: none;
}
.ant-tabs-content-holder {
border-left: 0.5px solid var(--color-border);
border-right: 0.5px solid var(--color-border);
}
.ant-tabs-ink-bar {
display: none;
}
`
export default AgentsPage export default AgentsPage

View File

@ -106,7 +106,7 @@ const MessageItem: FC<Props> = ({
topic, topic,
onResponse: (msg) => { onResponse: (msg) => {
setMessage(msg) setMessage(msg)
if (msg.status === 'success') { if (msg.status !== 'pending') {
const _messages = messages.map((m) => (m.id === msg.id ? msg : m)) const _messages = messages.map((m) => (m.id === msg.id ? msg : m))
onSetMessages(_messages) onSetMessages(_messages)
db.topics.update(topic.id, { messages: _messages }) db.topics.update(topic.id, { messages: _messages })