mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-20 04:28:09 -05:00
Compare commits
1 Commits
make-old-w
...
cursor/SEC
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8cc94acb6 |
@@ -91,12 +91,15 @@ async def list_library_agents(
|
||||
]
|
||||
|
||||
# Determine sorting
|
||||
order_by: prisma.types.LibraryAgentOrderByInput | None = None
|
||||
order_by: list[prisma.types.LibraryAgentOrderByInput] | prisma.types.LibraryAgentOrderByInput | None = None
|
||||
|
||||
if sort_by == library_model.LibraryAgentSort.CREATED_AT:
|
||||
order_by = {"createdAt": "asc"}
|
||||
elif sort_by == library_model.LibraryAgentSort.UPDATED_AT:
|
||||
order_by = {"updatedAt": "desc"}
|
||||
elif sort_by == library_model.LibraryAgentSort.FAVORITES_FIRST:
|
||||
# Sort by favorites first, then by updated date
|
||||
order_by = [{"isFavorite": "desc"}, {"updatedAt": "desc"}]
|
||||
|
||||
try:
|
||||
library_agents = await prisma.models.LibraryAgent.prisma().find_many(
|
||||
|
||||
@@ -64,6 +64,9 @@ class LibraryAgent(pydantic.BaseModel):
|
||||
# Indicates if this agent is the latest version
|
||||
is_latest_version: bool
|
||||
|
||||
# Indicates if this agent is marked as favorite by the user
|
||||
is_favorite: bool
|
||||
|
||||
@staticmethod
|
||||
def from_db(
|
||||
agent: prisma.models.LibraryAgent,
|
||||
@@ -130,6 +133,7 @@ class LibraryAgent(pydantic.BaseModel):
|
||||
new_output=new_output,
|
||||
can_access_graph=can_access_graph,
|
||||
is_latest_version=is_latest_version,
|
||||
is_favorite=agent.isFavorite,
|
||||
)
|
||||
|
||||
|
||||
@@ -314,6 +318,7 @@ class LibraryAgentSort(str, Enum):
|
||||
|
||||
CREATED_AT = "createdAt"
|
||||
UPDATED_AT = "updatedAt"
|
||||
FAVORITES_FIRST = "favoritesFirst"
|
||||
|
||||
|
||||
class LibraryAgentUpdateRequest(pydantic.BaseModel):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { useState } from "react";
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { LibraryAgent } from "@/lib/autogpt-server-api/types";
|
||||
import { useAgentRunModal } from "./useAgentRunModal";
|
||||
import { ModalHeader } from "./components/ModalHeader/ModalHeader";
|
||||
import { AgentCostSection } from "./components/AgentCostSection/AgentCostSection";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { LibraryAgent } from "@/lib/autogpt-server-api/types";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { Badge } from "@/components/atoms/Badge/Badge";
|
||||
import { formatDate } from "@/lib/utils/time";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Input } from "@/components/atoms/Input/Input";
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { LibraryAgent } from "@/lib/autogpt-server-api/types";
|
||||
|
||||
interface Props {
|
||||
agent: LibraryAgent;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Badge } from "@/components/atoms/Badge/Badge";
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { LibraryAgent } from "@/lib/autogpt-server-api/types";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { ShowMoreText } from "@/components/molecules/ShowMoreText/ShowMoreText";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { createContext, useContext } from "react";
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { LibraryAgent } from "@/lib/autogpt-server-api/types";
|
||||
import { RunVariant } from "./useAgentRunModal";
|
||||
|
||||
export interface RunAgentModalContextValue {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { LibraryAgent } from "@/lib/autogpt-server-api/types";
|
||||
import { useState, useCallback, useMemo } from "react";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { isEmpty } from "@/lib/utils";
|
||||
@@ -7,7 +7,7 @@ import { usePostV1CreateExecutionSchedule as useCreateSchedule } from "@/app/api
|
||||
import { usePostV2SetupTrigger } from "@/app/api/__generated__/endpoints/presets/presets";
|
||||
import { ExecuteGraphResponse } from "@/app/api/__generated__/models/executeGraphResponse";
|
||||
import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
|
||||
import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset";
|
||||
import { LibraryAgentPreset } from "@/lib/autogpt-server-api/types";
|
||||
|
||||
export type RunVariant =
|
||||
| "manual"
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { useGetV2GetLibraryAgent } from "@/app/api/__generated__/endpoints/library/library";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { useParams } from "next/navigation";
|
||||
|
||||
export function useAgentRunsView() {
|
||||
const { id } = useParams();
|
||||
const agentId = id as string;
|
||||
const { data: response, isSuccess, error } = useGetV2GetLibraryAgent(agentId);
|
||||
const api = useBackendAPI();
|
||||
|
||||
const { data: response, isSuccess, error } = useQuery({
|
||||
queryKey: ["v2", "get", "library", "agent", agentId],
|
||||
queryFn: () => api.getLibraryAgent(agentId),
|
||||
enabled: !!agentId,
|
||||
});
|
||||
|
||||
return {
|
||||
agentId: id,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Heart } from "@phosphor-icons/react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { LibraryAgent } from "@/lib/autogpt-server-api/types";
|
||||
import { useFavoriteAgent } from "./useFavoriteAgent";
|
||||
|
||||
interface LibraryAgentCardProps {
|
||||
agent: LibraryAgent;
|
||||
@@ -17,8 +20,23 @@ export default function LibraryAgentCard({
|
||||
can_access_graph,
|
||||
creator_image_url,
|
||||
image_url,
|
||||
is_favorite,
|
||||
},
|
||||
}: LibraryAgentCardProps) {
|
||||
const [isFavorite, setIsFavorite] = useState(is_favorite);
|
||||
const { toggleFavorite } = useFavoriteAgent();
|
||||
|
||||
const handleFavoriteClick = async (e: React.MouseEvent) => {
|
||||
e.preventDefault(); // Prevent navigation to agent page
|
||||
e.stopPropagation();
|
||||
|
||||
try {
|
||||
const newFavoriteStatus = await toggleFavorite(id, isFavorite);
|
||||
setIsFavorite(newFavoriteStatus);
|
||||
} catch (error) {
|
||||
// Error is already handled in the hook
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
data-testid="library-agent-card"
|
||||
@@ -67,6 +85,19 @@ export default function LibraryAgentCard({
|
||||
<AvatarFallback size={64}>{name.charAt(0)}</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
|
||||
{/* Favorite heart icon */}
|
||||
<button
|
||||
onClick={handleFavoriteClick}
|
||||
className="absolute top-4 right-4 rounded-full bg-white/90 p-2 shadow-sm transition-all duration-200 hover:bg-white hover:scale-110 dark:bg-gray-800/90 dark:hover:bg-gray-700"
|
||||
aria-label={isFavorite ? "Remove from favorites" : "Add to favorites"}
|
||||
>
|
||||
<Heart
|
||||
size={20}
|
||||
weight={isFavorite ? "fill" : "regular"}
|
||||
className={isFavorite ? "text-red-500" : "text-gray-400 hover:text-red-500"}
|
||||
/>
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<div className="flex w-full flex-1 flex-col px-4 py-4">
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useCallback } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { LibraryAgentID } from "@/lib/autogpt-server-api/types";
|
||||
|
||||
export function useFavoriteAgent() {
|
||||
const api = useBackendAPI();
|
||||
const { toast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const toggleFavorite = useCallback(
|
||||
async (agentId: LibraryAgentID, currentFavoriteStatus: boolean) => {
|
||||
const newFavoriteStatus = !currentFavoriteStatus;
|
||||
|
||||
// Optimistic update - update the cache immediately for all library agent queries
|
||||
queryClient.setQueriesData(
|
||||
{
|
||||
predicate: (query) => query.queryKey[0] === "v2" &&
|
||||
query.queryKey[1] === "list" &&
|
||||
query.queryKey[2] === "library" &&
|
||||
query.queryKey[3] === "agents",
|
||||
},
|
||||
(oldData: any) => {
|
||||
if (!oldData?.pages) return oldData;
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
pages: oldData.pages.map((page: any) => ({
|
||||
...page,
|
||||
agents: page.agents.map((agent: any) =>
|
||||
agent.id === agentId
|
||||
? { ...agent, is_favorite: newFavoriteStatus }
|
||||
: agent
|
||||
),
|
||||
})),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await api.updateLibraryAgent(agentId, {
|
||||
is_favorite: newFavoriteStatus,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: newFavoriteStatus ? "Added to favorites" : "Removed from favorites",
|
||||
description: newFavoriteStatus
|
||||
? "Agent has been added to your favorites"
|
||||
: "Agent has been removed from your favorites",
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
// Invalidate the library agents query to refresh the list with proper sorting
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query) => query.queryKey[0] === "v2" &&
|
||||
query.queryKey[1] === "list" &&
|
||||
query.queryKey[2] === "library" &&
|
||||
query.queryKey[3] === "agents",
|
||||
});
|
||||
|
||||
return newFavoriteStatus;
|
||||
} catch (error) {
|
||||
// Revert optimistic update on error for all library agent queries
|
||||
queryClient.setQueriesData(
|
||||
{
|
||||
predicate: (query) => query.queryKey[0] === "v2" &&
|
||||
query.queryKey[1] === "list" &&
|
||||
query.queryKey[2] === "library" &&
|
||||
query.queryKey[3] === "agents",
|
||||
},
|
||||
(oldData: any) => {
|
||||
if (!oldData?.pages) return oldData;
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
pages: oldData.pages.map((page: any) => ({
|
||||
...page,
|
||||
agents: page.agents.map((agent: any) =>
|
||||
agent.id === agentId
|
||||
? { ...agent, is_favorite: currentFavoriteStatus }
|
||||
: agent
|
||||
),
|
||||
})),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
console.error("Failed to update favorite status:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to update favorite status. Please try again.",
|
||||
duration: 3000,
|
||||
variant: "destructive",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[api, toast, queryClient]
|
||||
);
|
||||
|
||||
return { toggleFavorite };
|
||||
}
|
||||
@@ -1,44 +1,46 @@
|
||||
import { useGetV2ListLibraryAgentsInfinite } from "@/app/api/__generated__/endpoints/library/library";
|
||||
import { LibraryAgentResponse } from "@/app/api/__generated__/models/libraryAgentResponse";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { LibraryAgentResponse } from "@/lib/autogpt-server-api/types";
|
||||
import { useLibraryPageContext } from "../state-provider";
|
||||
|
||||
export const useLibraryAgentList = () => {
|
||||
const { searchTerm, librarySort } = useLibraryPageContext();
|
||||
const api = useBackendAPI();
|
||||
|
||||
const {
|
||||
data: agents,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isLoading: agentLoading,
|
||||
} = useGetV2ListLibraryAgentsInfinite(
|
||||
{
|
||||
page: 1,
|
||||
page_size: 8,
|
||||
search_term: searchTerm || undefined,
|
||||
sort_by: librarySort,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: ["v2", "list", "library", "agents", searchTerm, librarySort],
|
||||
queryFn: async ({ pageParam = 1 }) => {
|
||||
return await api.listLibraryAgents({
|
||||
page: pageParam,
|
||||
page_size: 8,
|
||||
search_term: searchTerm || undefined,
|
||||
sort_by: librarySort,
|
||||
});
|
||||
},
|
||||
{
|
||||
query: {
|
||||
getNextPageParam: (lastPage) => {
|
||||
const pagination = (lastPage.data as LibraryAgentResponse).pagination;
|
||||
const isMore =
|
||||
pagination.current_page * pagination.page_size <
|
||||
pagination.total_items;
|
||||
getNextPageParam: (lastPage) => {
|
||||
const pagination = lastPage.pagination;
|
||||
const isMore =
|
||||
pagination.current_page * pagination.page_size <
|
||||
pagination.total_items;
|
||||
|
||||
return isMore ? pagination.current_page + 1 : undefined;
|
||||
},
|
||||
},
|
||||
return isMore ? pagination.current_page + 1 : undefined;
|
||||
},
|
||||
);
|
||||
initialPageParam: 1,
|
||||
});
|
||||
|
||||
const allAgents =
|
||||
agents?.pages?.flatMap((page) => {
|
||||
const response = page.data as LibraryAgentResponse;
|
||||
return response.agents;
|
||||
return page.agents;
|
||||
}) ?? [];
|
||||
|
||||
const agentCount = agents?.pages?.[0]
|
||||
? (agents.pages[0].data as LibraryAgentResponse).pagination.total_items
|
||||
? agents.pages[0].pagination.total_items
|
||||
: 0;
|
||||
|
||||
return {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { LibraryAgentSort } from "@/app/api/__generated__/models/libraryAgentSort";
|
||||
import { LibraryAgentSortEnum as LibraryAgentSort } from "@/lib/autogpt-server-api/types";
|
||||
import { useLibrarySortMenu } from "./useLibrarySortMenu";
|
||||
|
||||
export default function LibrarySortMenu(): React.ReactNode {
|
||||
@@ -19,14 +19,17 @@ export default function LibrarySortMenu(): React.ReactNode {
|
||||
<Select onValueChange={handleSortChange}>
|
||||
<SelectTrigger className="ml-1 w-fit space-x-1 border-none px-0 text-base underline underline-offset-4 shadow-none">
|
||||
<ArrowDownNarrowWideIcon className="h-4 w-4 sm:hidden" />
|
||||
<SelectValue placeholder="Last Modified" />
|
||||
<SelectValue placeholder="Favorites First" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value={LibraryAgentSort.createdAt}>
|
||||
<SelectItem value={LibraryAgentSort.FAVORITES_FIRST}>
|
||||
Favorites First
|
||||
</SelectItem>
|
||||
<SelectItem value={LibraryAgentSort.CREATED_AT}>
|
||||
Creation Date
|
||||
</SelectItem>
|
||||
<SelectItem value={LibraryAgentSort.updatedAt}>
|
||||
<SelectItem value={LibraryAgentSort.UPDATED_AT}>
|
||||
Last Modified
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LibraryAgentSort } from "@/app/api/__generated__/models/libraryAgentSort";
|
||||
import { LibraryAgentSortEnum as LibraryAgentSort } from "@/lib/autogpt-server-api/types";
|
||||
import { useLibraryPageContext } from "../state-provider";
|
||||
|
||||
export const useLibrarySortMenu = () => {
|
||||
@@ -11,12 +11,14 @@ export const useLibrarySortMenu = () => {
|
||||
|
||||
const getSortLabel = (sort: LibraryAgentSort) => {
|
||||
switch (sort) {
|
||||
case LibraryAgentSort.createdAt:
|
||||
case LibraryAgentSort.CREATED_AT:
|
||||
return "Creation Date";
|
||||
case LibraryAgentSort.updatedAt:
|
||||
case LibraryAgentSort.UPDATED_AT:
|
||||
return "Last Modified";
|
||||
case LibraryAgentSort.FAVORITES_FIRST:
|
||||
return "Favorites First";
|
||||
default:
|
||||
return "Last Modified";
|
||||
return "Favorites First";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { LibraryAgentSort } from "@/app/api/__generated__/models/libraryAgentSort";
|
||||
import { LibraryAgentSortEnum as LibraryAgentSort } from "@/lib/autogpt-server-api/types";
|
||||
import {
|
||||
createContext,
|
||||
useState,
|
||||
@@ -31,7 +31,7 @@ export function LibraryPageStateProvider({
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
|
||||
const [librarySort, setLibrarySort] = useState<LibraryAgentSort>(
|
||||
LibraryAgentSort.updatedAt,
|
||||
LibraryAgentSort.FAVORITES_FIRST,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -428,6 +428,7 @@ export type LibraryAgent = {
|
||||
new_output: boolean;
|
||||
can_access_graph: boolean;
|
||||
is_latest_version: boolean;
|
||||
is_favorite: boolean;
|
||||
} & (
|
||||
| {
|
||||
has_external_trigger: true;
|
||||
@@ -502,6 +503,7 @@ export type LibraryAgentPresetUpdatable = Partial<
|
||||
export enum LibraryAgentSortEnum {
|
||||
CREATED_AT = "createdAt",
|
||||
UPDATED_AT = "updatedAt",
|
||||
FAVORITES_FIRST = "favoritesFirst",
|
||||
}
|
||||
|
||||
/* *** CREDENTIALS *** */
|
||||
|
||||
Reference in New Issue
Block a user