mirror of
https://github.com/arc53/DocsGPT.git
synced 2026-01-20 05:50:58 +00:00
feat: agent search functionality with filters and loading states (#2179)
* feat: implement agent search functionality with filters and loading states * style: improve layout and styling of agent search input and description
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import Search from '../assets/search.svg';
|
||||
import Spinner from '../components/Spinner';
|
||||
import {
|
||||
setConversation,
|
||||
@@ -10,19 +11,40 @@ import {
|
||||
} from '../conversation/conversationSlice';
|
||||
import {
|
||||
selectSelectedAgent,
|
||||
selectToken,
|
||||
setSelectedAgent,
|
||||
} from '../preferences/preferenceSlice';
|
||||
import AgentCard from './AgentCard';
|
||||
import { agentSectionsConfig } from './agents.config';
|
||||
import { AgentSectionId, agentSectionsConfig } from './agents.config';
|
||||
import { AgentFilterTab, useAgentSearch } from './hooks/useAgentSearch';
|
||||
import { useAgentsFetch } from './hooks/useAgentsFetch';
|
||||
import { Agent } from './types';
|
||||
|
||||
const FILTER_TABS: { id: AgentFilterTab; labelKey: string }[] = [
|
||||
{ id: 'all', labelKey: 'agents.filters.all' },
|
||||
{ id: 'template', labelKey: 'agents.filters.byDocsGPT' },
|
||||
{ id: 'user', labelKey: 'agents.filters.byMe' },
|
||||
{ id: 'shared', labelKey: 'agents.filters.shared' },
|
||||
];
|
||||
|
||||
export default function AgentsList() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const token = useSelector(selectToken);
|
||||
const selectedAgent = useSelector(selectSelectedAgent);
|
||||
|
||||
const { isLoading } = useAgentsFetch();
|
||||
|
||||
const {
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
activeFilter,
|
||||
setActiveFilter,
|
||||
filteredAgentsBySection,
|
||||
totalAgentsBySection,
|
||||
hasAnyAgents,
|
||||
hasFilteredResults,
|
||||
isDataLoaded,
|
||||
} = useAgentSearch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setConversation([]));
|
||||
dispatch(
|
||||
@@ -31,57 +53,150 @@ export default function AgentsList() {
|
||||
}),
|
||||
);
|
||||
if (selectedAgent) dispatch(setSelectedAgent(null));
|
||||
}, [token]);
|
||||
}, []);
|
||||
|
||||
const visibleSections = agentSectionsConfig.filter((config) => {
|
||||
if (activeFilter !== 'all') {
|
||||
return config.id === activeFilter;
|
||||
}
|
||||
const sectionId = config.id as AgentSectionId;
|
||||
const hasAgentsInSection = totalAgentsBySection[sectionId] > 0;
|
||||
const hasFilteredAgents = filteredAgentsBySection[sectionId].length > 0;
|
||||
const sectionDataLoaded = isDataLoaded[sectionId];
|
||||
|
||||
if (!sectionDataLoaded) return true;
|
||||
if (searchQuery) return hasFilteredAgents;
|
||||
if (config.id === 'user') return true;
|
||||
return hasAgentsInSection;
|
||||
});
|
||||
|
||||
const showSearchEmptyState =
|
||||
searchQuery &&
|
||||
hasAnyAgents &&
|
||||
!hasFilteredResults &&
|
||||
activeFilter === 'all';
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-12">
|
||||
<h1 className="text-eerie-black mb-0 text-[32px] font-bold lg:text-[40px] dark:text-[#E0E0E0]">
|
||||
{t('agents.title')}
|
||||
</h1>
|
||||
<p className="dark:text-gray-4000 mt-5 text-[15px] text-[#71717A]">
|
||||
<p className="dark:text-gray-4000 mt-5 max-w-lg text-[15px] leading-6 text-[#71717A]">
|
||||
{t('agents.description')}
|
||||
</p>
|
||||
{agentSectionsConfig.map((sectionConfig) => (
|
||||
<AgentSection key={sectionConfig.id} config={sectionConfig} />
|
||||
|
||||
<div className="mt-6 flex flex-col gap-4 pb-4">
|
||||
<div className="relative w-full max-w-md">
|
||||
<img
|
||||
src={Search}
|
||||
alt=""
|
||||
className="absolute top-1/2 left-4 h-5 w-5 -translate-y-1/2 opacity-40"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={t('agents.searchPlaceholder')}
|
||||
className="h-[44px] w-full rounded-full border border-[#E5E5E5] bg-white py-2 pr-5 pl-11 text-sm shadow-[0_1px_4px_rgba(0,0,0,0.06)] transition-shadow outline-none placeholder:text-[#9CA3AF] focus:shadow-[0_2px_8px_rgba(0,0,0,0.1)] dark:border-[#3A3A3A] dark:bg-[#2C2C2C] dark:text-white dark:shadow-none dark:placeholder:text-[#6B7280]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{FILTER_TABS.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveFilter(tab.id)}
|
||||
className={`rounded-full px-4 py-2 text-sm transition-colors ${
|
||||
activeFilter === tab.id
|
||||
? 'bg-[#E0E0E0] text-[#18181B] dark:bg-[#4A4A4A] dark:text-white'
|
||||
: 'bg-transparent text-[#71717A] hover:bg-[#F5F5F5] dark:text-[#949494] dark:hover:bg-[#383838]/50'
|
||||
}`}
|
||||
>
|
||||
{t(tab.labelKey)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{visibleSections.map((sectionConfig) => (
|
||||
<AgentSection
|
||||
key={sectionConfig.id}
|
||||
config={sectionConfig}
|
||||
filteredAgents={
|
||||
filteredAgentsBySection[sectionConfig.id as AgentSectionId]
|
||||
}
|
||||
totalAgents={totalAgentsBySection[sectionConfig.id as AgentSectionId]}
|
||||
searchQuery={searchQuery}
|
||||
isFilteredView={activeFilter !== 'all'}
|
||||
isLoading={isLoading[sectionConfig.id as AgentSectionId]}
|
||||
/>
|
||||
))}
|
||||
|
||||
{showSearchEmptyState && (
|
||||
<div className="mt-12 flex flex-col items-center justify-center gap-2 text-[#71717A]">
|
||||
<p className="text-lg">{t('agents.noSearchResults')}</p>
|
||||
<p className="text-sm">{t('agents.tryDifferentSearch')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface AgentSectionProps {
|
||||
config: (typeof agentSectionsConfig)[number];
|
||||
filteredAgents: Agent[];
|
||||
totalAgents: number;
|
||||
searchQuery: string;
|
||||
isFilteredView: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
function AgentSection({
|
||||
config,
|
||||
}: {
|
||||
config: (typeof agentSectionsConfig)[number];
|
||||
}) {
|
||||
filteredAgents,
|
||||
totalAgents,
|
||||
searchQuery,
|
||||
isFilteredView,
|
||||
isLoading,
|
||||
}: AgentSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const token = useSelector(selectToken);
|
||||
const agents = useSelector(config.selectData);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const allAgents = useSelector(config.selectData);
|
||||
|
||||
const updateAgents = (updatedAgents: Agent[]) => {
|
||||
dispatch(config.updateAction(updatedAgents));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const getAgents = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await config.fetchAgents(token);
|
||||
if (!response.ok)
|
||||
throw new Error(`Failed to fetch ${config.id} agents`);
|
||||
const data = await response.json();
|
||||
dispatch(config.updateAction(data));
|
||||
} catch (error) {
|
||||
console.error(`Error fetching ${config.id} agents:`, error);
|
||||
dispatch(config.updateAction([]));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
getAgents();
|
||||
}, [token, config, dispatch]);
|
||||
const hasNoAgentsAtAll = !isLoading && totalAgents === 0;
|
||||
const isSearchingWithNoResults =
|
||||
!isLoading && searchQuery && filteredAgents.length === 0 && totalAgents > 0;
|
||||
|
||||
if (isFilteredView && isSearchingWithNoResults) {
|
||||
return (
|
||||
<div className="mt-12 flex flex-col items-center justify-center gap-2 text-[#71717A]">
|
||||
<p className="text-lg">{t('agents.noSearchResults')}</p>
|
||||
<p className="text-sm">{t('agents.tryDifferentSearch')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isFilteredView && hasNoAgentsAtAll) {
|
||||
return (
|
||||
<div className="mt-12 flex flex-col items-center justify-center gap-3 text-[#71717A]">
|
||||
<p>{t(`agents.sections.${config.id}.emptyState`)}</p>
|
||||
{config.showNewAgentButton && (
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-full px-4 py-2 text-sm text-white"
|
||||
onClick={() => navigate('/agents/new')}
|
||||
>
|
||||
{t('agents.newAgent')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-8 flex flex-col gap-4">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
@@ -103,24 +218,24 @@ function AgentSection({
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{loading ? (
|
||||
<div className="flex h-72 w-full items-center justify-center">
|
||||
{isLoading ? (
|
||||
<div className="flex h-40 w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : agents && agents.length > 0 ? (
|
||||
) : filteredAgents.length > 0 ? (
|
||||
<div className="grid grid-cols-1 gap-4 sm:flex sm:flex-wrap">
|
||||
{agents.map((agent) => (
|
||||
{filteredAgents.map((agent) => (
|
||||
<AgentCard
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
agents={agents}
|
||||
agents={allAgents || []}
|
||||
updateAgents={updateAgents}
|
||||
section={config.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-72 w-full flex-col items-center justify-center gap-3 text-base text-[#18181B] dark:text-[#E0E0E0]">
|
||||
) : hasNoAgentsAtAll ? (
|
||||
<div className="flex h-40 w-full flex-col items-center justify-center gap-3 text-[#71717A]">
|
||||
<p>{t(`agents.sections.${config.id}.emptyState`)}</p>
|
||||
{config.showNewAgentButton && (
|
||||
<button
|
||||
@@ -131,7 +246,7 @@ function AgentSection({
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import userService from '../api/services/userService';
|
||||
import {
|
||||
selectAgents,
|
||||
selectTemplateAgents,
|
||||
selectSharedAgents,
|
||||
selectTemplateAgents,
|
||||
setAgents,
|
||||
setTemplateAgents,
|
||||
setSharedAgents,
|
||||
setTemplateAgents,
|
||||
} from '../preferences/preferenceSlice';
|
||||
|
||||
export type AgentSectionId = 'template' | 'user' | 'shared';
|
||||
|
||||
export const agentSectionsConfig = [
|
||||
{
|
||||
id: 'template',
|
||||
id: 'template' as const,
|
||||
title: 'By DocsGPT',
|
||||
description: 'Agents provided by DocsGPT',
|
||||
showNewAgentButton: false,
|
||||
@@ -20,7 +22,7 @@ export const agentSectionsConfig = [
|
||||
updateAction: setTemplateAgents,
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
id: 'user' as const,
|
||||
title: 'By me',
|
||||
description: 'Agents created or published by you',
|
||||
showNewAgentButton: true,
|
||||
@@ -30,7 +32,7 @@ export const agentSectionsConfig = [
|
||||
updateAction: setAgents,
|
||||
},
|
||||
{
|
||||
id: 'shared',
|
||||
id: 'shared' as const,
|
||||
title: 'Shared with me',
|
||||
description: 'Agents imported by using a public link',
|
||||
showNewAgentButton: false,
|
||||
|
||||
122
frontend/src/agents/hooks/useAgentSearch.ts
Normal file
122
frontend/src/agents/hooks/useAgentSearch.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
selectAgents,
|
||||
selectSharedAgents,
|
||||
selectTemplateAgents,
|
||||
} from '../../preferences/preferenceSlice';
|
||||
import { AgentSectionId } from '../agents.config';
|
||||
import { Agent } from '../types';
|
||||
|
||||
export type AgentFilterTab = 'all' | AgentSectionId;
|
||||
|
||||
export type AgentsBySection = Record<AgentSectionId, Agent[]>;
|
||||
|
||||
interface UseAgentSearchResult {
|
||||
searchQuery: string;
|
||||
setSearchQuery: (query: string) => void;
|
||||
activeFilter: AgentFilterTab;
|
||||
setActiveFilter: (filter: AgentFilterTab) => void;
|
||||
filteredAgentsBySection: AgentsBySection;
|
||||
totalAgentsBySection: Record<AgentSectionId, number>;
|
||||
hasAnyAgents: boolean;
|
||||
hasFilteredResults: boolean;
|
||||
isDataLoaded: Record<AgentSectionId, boolean>;
|
||||
}
|
||||
|
||||
const filterAgentsByQuery = (
|
||||
agents: Agent[] | null,
|
||||
query: string,
|
||||
): Agent[] => {
|
||||
if (!agents) return [];
|
||||
if (!query.trim()) return agents;
|
||||
|
||||
const normalizedQuery = query.toLowerCase().trim();
|
||||
return agents.filter(
|
||||
(agent) =>
|
||||
agent.name.toLowerCase().includes(normalizedQuery) ||
|
||||
agent.description?.toLowerCase().includes(normalizedQuery),
|
||||
);
|
||||
};
|
||||
|
||||
export function useAgentSearch(): UseAgentSearchResult {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeFilter, setActiveFilter] = useState<AgentFilterTab>('all');
|
||||
|
||||
const templateAgents = useSelector(selectTemplateAgents);
|
||||
const userAgents = useSelector(selectAgents);
|
||||
const sharedAgents = useSelector(selectSharedAgents);
|
||||
|
||||
const handleSearchChange = useCallback((query: string) => {
|
||||
setSearchQuery(query);
|
||||
}, []);
|
||||
|
||||
const handleFilterChange = useCallback((filter: AgentFilterTab) => {
|
||||
setActiveFilter(filter);
|
||||
}, []);
|
||||
|
||||
const isDataLoaded = useMemo(
|
||||
(): Record<AgentSectionId, boolean> => ({
|
||||
template: templateAgents !== null,
|
||||
user: userAgents !== null,
|
||||
shared: sharedAgents !== null,
|
||||
}),
|
||||
[templateAgents, userAgents, sharedAgents],
|
||||
);
|
||||
|
||||
const totalAgentsBySection = useMemo(
|
||||
(): Record<AgentSectionId, number> => ({
|
||||
template: templateAgents?.length ?? 0,
|
||||
user: userAgents?.length ?? 0,
|
||||
shared: sharedAgents?.length ?? 0,
|
||||
}),
|
||||
[templateAgents, userAgents, sharedAgents],
|
||||
);
|
||||
|
||||
const filteredAgentsBySection = useMemo((): AgentsBySection => {
|
||||
const filtered = {
|
||||
template: filterAgentsByQuery(templateAgents, searchQuery),
|
||||
user: filterAgentsByQuery(userAgents, searchQuery),
|
||||
shared: filterAgentsByQuery(sharedAgents, searchQuery),
|
||||
};
|
||||
|
||||
if (activeFilter === 'all') {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
return {
|
||||
template: activeFilter === 'template' ? filtered.template : [],
|
||||
user: activeFilter === 'user' ? filtered.user : [],
|
||||
shared: activeFilter === 'shared' ? filtered.shared : [],
|
||||
};
|
||||
}, [templateAgents, userAgents, sharedAgents, searchQuery, activeFilter]);
|
||||
|
||||
const hasAnyAgents = useMemo(() => {
|
||||
return (
|
||||
totalAgentsBySection.template > 0 ||
|
||||
totalAgentsBySection.user > 0 ||
|
||||
totalAgentsBySection.shared > 0
|
||||
);
|
||||
}, [totalAgentsBySection]);
|
||||
|
||||
const hasFilteredResults = useMemo(() => {
|
||||
return (
|
||||
filteredAgentsBySection.template.length > 0 ||
|
||||
filteredAgentsBySection.user.length > 0 ||
|
||||
filteredAgentsBySection.shared.length > 0
|
||||
);
|
||||
}, [filteredAgentsBySection]);
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
setSearchQuery: handleSearchChange,
|
||||
activeFilter,
|
||||
setActiveFilter: handleFilterChange,
|
||||
filteredAgentsBySection,
|
||||
totalAgentsBySection,
|
||||
hasAnyAgents,
|
||||
hasFilteredResults,
|
||||
isDataLoaded,
|
||||
};
|
||||
}
|
||||
83
frontend/src/agents/hooks/useAgentsFetch.ts
Normal file
83
frontend/src/agents/hooks/useAgentsFetch.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import userService from '../../api/services/userService';
|
||||
import {
|
||||
selectToken,
|
||||
setAgents,
|
||||
setSharedAgents,
|
||||
setTemplateAgents,
|
||||
} from '../../preferences/preferenceSlice';
|
||||
import { AgentSectionId } from '../agents.config';
|
||||
|
||||
interface UseAgentsFetchResult {
|
||||
isLoading: Record<AgentSectionId, boolean>;
|
||||
isAllLoaded: boolean;
|
||||
}
|
||||
|
||||
export function useAgentsFetch(): UseAgentsFetchResult {
|
||||
const dispatch = useDispatch();
|
||||
const token = useSelector(selectToken);
|
||||
|
||||
const [isLoading, setIsLoading] = useState<Record<AgentSectionId, boolean>>({
|
||||
template: true,
|
||||
user: true,
|
||||
shared: true,
|
||||
});
|
||||
|
||||
const fetchTemplateAgents = useCallback(async () => {
|
||||
try {
|
||||
const response = await userService.getTemplateAgents(token);
|
||||
if (!response.ok) throw new Error('Failed to fetch template agents');
|
||||
const data = await response.json();
|
||||
dispatch(setTemplateAgents(data));
|
||||
} catch (error) {
|
||||
dispatch(setTemplateAgents([]));
|
||||
} finally {
|
||||
setIsLoading((prev) => ({ ...prev, template: false }));
|
||||
}
|
||||
}, [token, dispatch]);
|
||||
|
||||
const fetchUserAgents = useCallback(async () => {
|
||||
try {
|
||||
const response = await userService.getAgents(token);
|
||||
if (!response.ok) throw new Error('Failed to fetch user agents');
|
||||
const data = await response.json();
|
||||
dispatch(setAgents(data));
|
||||
} catch (error) {
|
||||
dispatch(setAgents([]));
|
||||
} finally {
|
||||
setIsLoading((prev) => ({ ...prev, user: false }));
|
||||
}
|
||||
}, [token, dispatch]);
|
||||
|
||||
const fetchSharedAgents = useCallback(async () => {
|
||||
try {
|
||||
const response = await userService.getSharedAgents(token);
|
||||
if (!response.ok) throw new Error('Failed to fetch shared agents');
|
||||
const data = await response.json();
|
||||
dispatch(setSharedAgents(data));
|
||||
} catch (error) {
|
||||
dispatch(setSharedAgents([]));
|
||||
} finally {
|
||||
setIsLoading((prev) => ({ ...prev, shared: false }));
|
||||
}
|
||||
}, [token, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading({ template: true, user: true, shared: true });
|
||||
Promise.all([
|
||||
fetchTemplateAgents(),
|
||||
fetchUserAgents(),
|
||||
fetchSharedAgents(),
|
||||
]);
|
||||
}, [fetchTemplateAgents, fetchUserAgents, fetchSharedAgents]);
|
||||
|
||||
const isAllLoaded =
|
||||
!isLoading.template && !isLoading.user && !isLoading.shared;
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isAllLoaded,
|
||||
};
|
||||
}
|
||||
@@ -491,6 +491,15 @@
|
||||
"description": "Entdecke und erstelle benutzerdefinierte Versionen von DocsGPT, die Anweisungen, zusätzliches Wissen und beliebige Kombinationen von Fähigkeiten kombinieren",
|
||||
"newAgent": "Neuer Agent",
|
||||
"backToAll": "Zurück zu allen Agenten",
|
||||
"searchPlaceholder": "Suchen...",
|
||||
"noSearchResults": "Keine Agenten gefunden",
|
||||
"tryDifferentSearch": "Versuche einen anderen Suchbegriff",
|
||||
"filters": {
|
||||
"all": "Alle",
|
||||
"byDocsGPT": "Von DocsGPT",
|
||||
"byMe": "Von mir",
|
||||
"shared": "Mit mir geteilt"
|
||||
},
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "Von DocsGPT",
|
||||
|
||||
@@ -491,6 +491,15 @@
|
||||
"description": "Discover and create custom versions of DocsGPT that combine instructions, extra knowledge, and any combination of skills",
|
||||
"newAgent": "New Agent",
|
||||
"backToAll": "Back to all agents",
|
||||
"searchPlaceholder": "Search...",
|
||||
"noSearchResults": "No agents found",
|
||||
"tryDifferentSearch": "Try a different search term",
|
||||
"filters": {
|
||||
"all": "All",
|
||||
"byDocsGPT": "By DocsGPT",
|
||||
"byMe": "By Me",
|
||||
"shared": "Shared With Me"
|
||||
},
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "By DocsGPT",
|
||||
|
||||
@@ -491,6 +491,15 @@
|
||||
"description": "Descubre y crea versiones personalizadas de DocsGPT que combinan instrucciones, conocimiento adicional y cualquier combinación de habilidades",
|
||||
"newAgent": "Nuevo Agente",
|
||||
"backToAll": "Volver a todos los agentes",
|
||||
"searchPlaceholder": "Buscar...",
|
||||
"noSearchResults": "No se encontraron agentes",
|
||||
"tryDifferentSearch": "Prueba con un término de búsqueda diferente",
|
||||
"filters": {
|
||||
"all": "Todos",
|
||||
"byDocsGPT": "Por DocsGPT",
|
||||
"byMe": "Por mí",
|
||||
"shared": "Compartidos conmigo"
|
||||
},
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "Por DocsGPT",
|
||||
|
||||
@@ -491,6 +491,15 @@
|
||||
"description": "指示、追加知識、スキルの組み合わせを含むDocsGPTのカスタムバージョンを発見して作成します",
|
||||
"newAgent": "新しいエージェント",
|
||||
"backToAll": "すべてのエージェントに戻る",
|
||||
"searchPlaceholder": "検索...",
|
||||
"noSearchResults": "エージェントが見つかりません",
|
||||
"tryDifferentSearch": "別の検索語をお試しください",
|
||||
"filters": {
|
||||
"all": "すべて",
|
||||
"byDocsGPT": "DocsGPT提供",
|
||||
"byMe": "自分の",
|
||||
"shared": "共有された"
|
||||
},
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "DocsGPT提供",
|
||||
|
||||
@@ -491,6 +491,15 @@
|
||||
"description": "Откройте и создайте пользовательские версии DocsGPT, которые объединяют инструкции, дополнительные знания и любую комбинацию навыков",
|
||||
"newAgent": "Новый Агент",
|
||||
"backToAll": "Вернуться ко всем агентам",
|
||||
"searchPlaceholder": "Поиск...",
|
||||
"noSearchResults": "Агенты не найдены",
|
||||
"tryDifferentSearch": "Попробуйте другой поисковый запрос",
|
||||
"filters": {
|
||||
"all": "Все",
|
||||
"byDocsGPT": "От DocsGPT",
|
||||
"byMe": "Мои",
|
||||
"shared": "Поделились со мной"
|
||||
},
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "От DocsGPT",
|
||||
|
||||
@@ -491,6 +491,15 @@
|
||||
"description": "探索並創建結合指令、額外知識和任意技能組合的DocsGPT自訂版本",
|
||||
"newAgent": "新建代理",
|
||||
"backToAll": "返回所有代理",
|
||||
"searchPlaceholder": "搜尋...",
|
||||
"noSearchResults": "未找到代理",
|
||||
"tryDifferentSearch": "請嘗試不同的搜尋詞",
|
||||
"filters": {
|
||||
"all": "全部",
|
||||
"byDocsGPT": "由DocsGPT提供",
|
||||
"byMe": "我的",
|
||||
"shared": "與我共享"
|
||||
},
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "由DocsGPT提供",
|
||||
|
||||
@@ -491,6 +491,15 @@
|
||||
"description": "发现并创建结合指令、额外知识和任意技能组合的DocsGPT自定义版本",
|
||||
"newAgent": "新建代理",
|
||||
"backToAll": "返回所有代理",
|
||||
"searchPlaceholder": "搜索...",
|
||||
"noSearchResults": "未找到代理",
|
||||
"tryDifferentSearch": "请尝试不同的搜索词",
|
||||
"filters": {
|
||||
"all": "全部",
|
||||
"byDocsGPT": "由DocsGPT提供",
|
||||
"byMe": "我的",
|
||||
"shared": "与我共享"
|
||||
},
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "由DocsGPT提供",
|
||||
|
||||
Reference in New Issue
Block a user