feat: add agents tabs and search
This commit is contained in:
parent
9ec0836d26
commit
ac21c90b6f
@ -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
@ -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)`
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 })
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user