添加智能体订阅功能 (#5954)

* 添加智能体订阅功能

* 修改图标

* 修改hook点

修改图标

* 优雅的引用图标

* feat(i18n): add settings title for agents in multiple languages

* fix(i18n): update translations for improved clarity

* Merge branch 'main' into Subscribe

---------

Co-authored-by: VM 96 <eov@88.com>
Co-authored-by: suyao <sy20010504@gmail.com>
This commit is contained in:
上房揭瓦 2025-05-13 23:09:38 +08:00 committed by GitHub
parent 8bd38ccd86
commit 71cd2def2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 105 additions and 9 deletions

View File

@ -44,6 +44,9 @@
"my_agents": "My Agents", "my_agents": "My Agents",
"search.no_results": "No results found", "search.no_results": "No results found",
"sorting.title": "Sorting", "sorting.title": "Sorting",
"settings": {
"title": "Agent Setting"
},
"tag.agent": "Agent", "tag.agent": "Agent",
"tag.default": "Default", "tag.default": "Default",
"tag.new": "New", "tag.new": "New",

View File

@ -48,7 +48,10 @@
"tag.default": "デフォルト", "tag.default": "デフォルト",
"tag.new": "新規", "tag.new": "新規",
"tag.system": "システム", "tag.system": "システム",
"title": "エージェント" "title": "エージェント",
"settings": {
"title": "エージェント設定"
}
}, },
"assistants": { "assistants": {
"title": "アシスタント", "title": "アシスタント",

View File

@ -48,6 +48,9 @@
}, },
"export": { "export": {
"agent": "Экспорт агента" "agent": "Экспорт агента"
},
"settings": {
"title": "Настройки агента"
} }
}, },
"assistants": { "assistants": {

View File

@ -48,7 +48,10 @@
"tag.default": "默认", "tag.default": "默认",
"tag.new": "新建", "tag.new": "新建",
"tag.system": "系统", "tag.system": "系统",
"title": "智能体" "title": "智能体",
"settings": {
"title": "智能体配置"
}
}, },
"assistants": { "assistants": {
"title": "助手", "title": "助手",

View File

@ -48,7 +48,10 @@
"tag.default": "預設", "tag.default": "預設",
"tag.new": "新增", "tag.new": "新增",
"tag.system": "系統", "tag.system": "系統",
"title": "智慧代理人" "title": "智慧代理人",
"settings": {
"title": "智慧代理人設定"
}
}, },
"assistants": { "assistants": {
"title": "助手", "title": "助手",

View File

@ -2,6 +2,8 @@ import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { Agent } from '@renderer/types' import { Agent } from '@renderer/types'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import store from '@renderer/store'
let _agents: Agent[] = [] let _agents: Agent[] = []
export const getAgentsFromSystemAgents = (systemAgents: any) => { export const getAgentsFromSystemAgents = (systemAgents: any) => {
@ -19,27 +21,44 @@ export function useSystemAgents() {
const { defaultAgent } = useSettings() const { defaultAgent } = useSettings()
const [agents, setAgents] = useState<Agent[]>([]) const [agents, setAgents] = useState<Agent[]>([])
const { resourcesPath } = useRuntime() const { resourcesPath } = useRuntime()
const { agentssubscribeUrl } = store.getState().settings
useEffect(() => { useEffect(() => {
const loadAgents = async () => { const loadAgents = async () => {
try { try {
// 始终加载本地 agents // 检查是否使用远程数据源
if (agentssubscribeUrl && agentssubscribeUrl.startsWith('http')) {
try {
await new Promise(resolve => setTimeout(resolve, 500));
const response = await fetch(agentssubscribeUrl);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const agentsData = await response.json() as Agent[];
setAgents(agentsData);
return;
} catch (error) {
console.error("Failed to load remote agents:", error);
// 远程加载失败,继续尝试加载本地数据
}
}
// 如果没有远程配置或获取失败,加载本地代理
if (resourcesPath && _agents.length === 0) { if (resourcesPath && _agents.length === 0) {
const localAgentsData = await window.api.fs.read(resourcesPath + '/data/agents.json') const localAgentsData = await window.api.fs.read(resourcesPath + '/data/agents.json')
_agents = JSON.parse(localAgentsData) as Agent[] _agents = JSON.parse(localAgentsData) as Agent[]
} }
// 如果没有远程配置或获取失败,使用本地 agents
setAgents(_agents) setAgents(_agents)
} catch (error) { } catch (error) {
console.error('Failed to load agents:', error) console.error('Failed to load agents:', error)
// 发生错误时使用本地 agents // 发生错误时使用已加载的本地 agents
setAgents(_agents) setAgents(_agents)
} }
} }
loadAgents() loadAgents()
}, [defaultAgent, resourcesPath]) }, [defaultAgent, resourcesPath, agentssubscribeUrl])
return agents return agents
} }

View File

@ -0,0 +1,47 @@
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store'
import { setAgentssubscribeUrl } from '@renderer/store/settings'
import Input from 'antd/es/input/Input'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
const AgentsSubscribeUrlSettings: FC = () => {
const { t } = useTranslation()
const { theme } = useTheme()
const dispatch = useAppDispatch()
const { agentssubscribeUrl } = useSettings()
const handleAgentChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setAgentssubscribeUrl(e.target.value))
}
return (
<SettingGroup theme={theme}>
<SettingTitle>
{t('agents.tag.agent')}
{t('settings.websearch.subscribe_add')}
</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.websearch.subscribe_url')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<Input
type="text"
value={agentssubscribeUrl || ''}
onChange={handleAgentChange}
style={{ width: 315 }}
placeholder={t('settings.websearch.subscribe_name.placeholder')}
/>
</HStack>
</SettingRow>
<SettingDivider />
</SettingGroup>
)
}
export default AgentsSubscribeUrlSettings

View File

@ -17,12 +17,13 @@ import { reset } from '@renderer/services/BackupService'
import { AppInfo } from '@renderer/types' import { AppInfo } from '@renderer/types'
import { formatFileSize } from '@renderer/utils' import { formatFileSize } from '@renderer/utils'
import { Button, Typography } from 'antd' import { Button, Typography } from 'antd'
import { FileText, FolderCog, FolderInput } from 'lucide-react' import { FileText, FolderCog, FolderInput, Sparkle } from 'lucide-react'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
import AgentsSubscribeUrlSettings from './AgentsSubscribeUrlSettings'
import ExportMenuOptions from './ExportMenuSettings' import ExportMenuOptions from './ExportMenuSettings'
import JoplinSettings from './JoplinSettings' import JoplinSettings from './JoplinSettings'
import MarkdownExportSettings from './MarkdownExportSettings' import MarkdownExportSettings from './MarkdownExportSettings'
@ -81,6 +82,7 @@ const DataSettings: FC = () => {
title: 'settings.data.markdown_export.title', title: 'settings.data.markdown_export.title',
icon: <FileText size={16} /> icon: <FileText size={16} />
}, },
{ key: 'divider_3', isDivider: true, text: t('settings.data.divider.third_party') }, { key: 'divider_3', isDivider: true, text: t('settings.data.divider.third_party') },
{ key: 'notion', title: 'settings.data.notion.title', icon: <i className="iconfont icon-notion" /> }, { key: 'notion', title: 'settings.data.notion.title', icon: <i className="iconfont icon-notion" /> },
{ {
@ -102,6 +104,11 @@ const DataSettings: FC = () => {
key: 'siyuan', key: 'siyuan',
title: 'settings.data.siyuan.title', title: 'settings.data.siyuan.title',
icon: <SiyuanIcon /> icon: <SiyuanIcon />
},
{
key: 'agentssubscribe_url',
title: 'agents.settings.title',
icon: <Sparkle size={16} className="icon" />
} }
] ]
@ -253,6 +260,7 @@ const DataSettings: FC = () => {
{menu === 'joplin' && <JoplinSettings />} {menu === 'joplin' && <JoplinSettings />}
{menu === 'obsidian' && <ObsidianSettings />} {menu === 'obsidian' && <ObsidianSettings />}
{menu === 'siyuan' && <SiyuanSettings />} {menu === 'siyuan' && <SiyuanSettings />}
{menu === 'agentssubscribe_url' && <AgentsSubscribeUrlSettings />}
</SettingContainer> </SettingContainer>
</Container> </Container>
) )

View File

@ -111,6 +111,8 @@ export interface SettingsState {
siyuanToken: string | null siyuanToken: string | null
siyuanBoxId: string | null siyuanBoxId: string | null
siyuanRootPath: string | null siyuanRootPath: string | null
// 订阅的助手地址
agentssubscribeUrl: string | null
// MinApps // MinApps
maxKeepAliveMinapps: number maxKeepAliveMinapps: number
showOpenedMinappsInSidebar: boolean showOpenedMinappsInSidebar: boolean
@ -218,6 +220,7 @@ export const initialState: SettingsState = {
siyuanToken: null, siyuanToken: null,
siyuanBoxId: null, siyuanBoxId: null,
siyuanRootPath: null, siyuanRootPath: null,
agentssubscribeUrl: '',
// MinApps // MinApps
maxKeepAliveMinapps: 3, maxKeepAliveMinapps: 3,
showOpenedMinappsInSidebar: true, showOpenedMinappsInSidebar: true,
@ -493,6 +496,9 @@ const settingsSlice = createSlice({
setSiyuanRootPath: (state, action: PayloadAction<string>) => { setSiyuanRootPath: (state, action: PayloadAction<string>) => {
state.siyuanRootPath = action.payload state.siyuanRootPath = action.payload
}, },
setAgentssubscribeUrl: (state, action: PayloadAction<string>) => {
state.agentssubscribeUrl = action.payload
},
setMaxKeepAliveMinapps: (state, action: PayloadAction<number>) => { setMaxKeepAliveMinapps: (state, action: PayloadAction<number>) => {
state.maxKeepAliveMinapps = action.payload state.maxKeepAliveMinapps = action.payload
}, },
@ -599,6 +605,7 @@ export const {
setSiyuanApiUrl, setSiyuanApiUrl,
setSiyuanToken, setSiyuanToken,
setSiyuanBoxId, setSiyuanBoxId,
setAgentssubscribeUrl,
setSiyuanRootPath, setSiyuanRootPath,
setMaxKeepAliveMinapps, setMaxKeepAliveMinapps,
setShowOpenedMinappsInSidebar, setShowOpenedMinappsInSidebar,