diff --git a/frontend-web/src/components/workspace/agents/agent-gallery.tsx b/frontend-web/src/components/workspace/agents/agent-gallery.tsx index 7e2b57c0..6d099a18 100644 --- a/frontend-web/src/components/workspace/agents/agent-gallery.tsx +++ b/frontend-web/src/components/workspace/agents/agent-gallery.tsx @@ -74,6 +74,7 @@ export function AgentGallery() { agents: mainAgents, total: mainTotal, isLoading: mainLoading, + isFetching: mainFetching, } = useAgentsPaginated({ scope: mainScope, search, @@ -89,6 +90,7 @@ export function AgentGallery() { agents: privateAgents, total: privateTotal, isLoading: isPrivateLoading, + isFetching: isPrivateFetching, error: privateError, } = usePrivateAgentsPaginated(privatePassword, { search, @@ -315,9 +317,16 @@ export function AgentGallery() { {/* Tabs */} - setActiveTab(v as "all" | "mine" | "private") - } + onValueChange={(v) => { + setActiveTab(v as "all" | "mine" | "private"); + // Each tab starts from a clean slate: switching tabs clears the + // keyword / tag / sort / publisher filters instead of carrying them + // over. The TagSearchBar's own input box is reset via its key below. + setSearch(""); + setTagIds([]); + setSort("time_desc"); + setPublisher(null); + }} className="flex flex-1 flex-col overflow-hidden" >
@@ -351,7 +360,11 @@ export function AgentGallery() {
void; - tagIds: string[]; - onTagIdsChange: (value: string[]) => void; + tagIds?: string[]; + onTagIdsChange?: (value: string[]) => void; className?: string; actions?: ReactNode; + /** Show an inline spinner while the caller's query is fetching. */ + searching?: boolean; + /** Hide the tag chip row for resources that have no tags. */ + hideTags?: boolean; + /** Override the search input placeholder. */ + searchPlaceholder?: string; }) { const { t } = useI18n(); const { data: tags } = useTags(targetType); @@ -47,11 +63,21 @@ export function TagSearchBar({ const [expanded, setExpanded] = useState(false); const cbRef = useRef(onSearchChange); cbRef.current = onSearchChange; + // Keep the pending debounce timer so an explicit search (Enter / 搜索 button) + // can cancel it and fire immediately instead of racing the 300ms delay. + const timerRef = useRef | null>(null); useEffect(() => { - const id = setTimeout(() => cbRef.current(input.trim()), 300); - return () => clearTimeout(id); + timerRef.current = setTimeout(() => cbRef.current(input.trim()), 300); + return () => { + if (timerRef.current) clearTimeout(timerRef.current); + }; }, [input]); + const flushSearch = () => { + if (timerRef.current) clearTimeout(timerRef.current); + cbRef.current(input.trim()); + }; + const allTags = tags ?? []; const canCollapse = allTags.length > COLLAPSE_THRESHOLD; // When collapsed, always keep the selected tags visible so the active @@ -62,7 +88,7 @@ export function TagSearchBar({ : allTags; const toggleTag = (id: string) => { - onTagIdsChange( + onTagIdsChange?.( tagIds.includes(id) ? tagIds.filter((x) => x !== id) : [...tagIds, id], ); }; @@ -75,8 +101,14 @@ export function TagSearchBar({ setInput(e.target.value)} - placeholder={t.adminTags.searchPlaceholder} - className="pl-8" + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + flushSearch(); + } + }} + placeholder={searchPlaceholder ?? t.adminTags.searchPlaceholder} + className={cn("pl-8", searching && "pr-9")} name="tag-search" // A `type="password"` field elsewhere on the page (e.g. the private // square access gate) makes browsers / password managers pick the @@ -88,7 +120,24 @@ export function TagSearchBar({ data-lpignore="true" data-form-type="other" /> + {searching && ( + + )} + {actions && (
{actions} @@ -96,11 +145,11 @@ export function TagSearchBar({ )}
- {allTags.length > 0 && ( + {!hideTags && allTags.length > 0 && (