1
1
"use client" ;
2
2
3
- import * as React from "react" ;
4
- import { IconPlay , StarRatingIcons } from "@/components/ui/icons" ;
3
+ import { StarRatingIcons } from "@/components/ui/icons" ;
5
4
import { Separator } from "@/components/ui/separator" ;
6
- import BackendAPI from "@/lib/autogpt-server-api" ;
5
+ import BackendAPI , { LibraryAgent } from "@/lib/autogpt-server-api" ;
7
6
import { useRouter } from "next/navigation" ;
8
7
import Link from "next/link" ;
9
8
import { useToast } from "@/components/ui/use-toast" ;
10
9
11
- import useSupabase from "@/hooks/useSupabase" ;
12
- import { DownloadIcon , LoaderIcon } from "lucide-react" ;
13
10
import { useOnboarding } from "../onboarding/onboarding-provider" ;
11
+ import { User } from "@supabase/supabase-js" ;
12
+ import { cn } from "@/lib/utils" ;
13
+ import { FC , useCallback , useMemo , useState } from "react" ;
14
+
14
15
interface AgentInfoProps {
16
+ user : User | null ;
15
17
name : string ;
16
18
creator : string ;
17
19
shortDescription : string ;
@@ -22,9 +24,11 @@ interface AgentInfoProps {
22
24
lastUpdated : string ;
23
25
version : string ;
24
26
storeListingVersionId : string ;
27
+ libraryAgent : LibraryAgent | null ;
25
28
}
26
29
27
- export const AgentInfo : React . FC < AgentInfoProps > = ( {
30
+ export const AgentInfo : FC < AgentInfoProps > = ( {
31
+ user,
28
32
name,
29
33
creator,
30
34
shortDescription,
@@ -35,28 +39,48 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
35
39
lastUpdated,
36
40
version,
37
41
storeListingVersionId,
42
+ libraryAgent,
38
43
} ) => {
39
44
const router = useRouter ( ) ;
40
- const api = React . useMemo ( ( ) => new BackendAPI ( ) , [ ] ) ;
41
- const { user } = useSupabase ( ) ;
45
+ const api = useMemo ( ( ) => new BackendAPI ( ) , [ ] ) ;
42
46
const { toast } = useToast ( ) ;
43
47
const { completeStep } = useOnboarding ( ) ;
44
-
45
- const [ downloading , setDownloading ] = React . useState ( false ) ;
46
-
47
- const handleAddToLibrary = async ( ) => {
48
+ const [ adding , setAdding ] = useState ( false ) ;
49
+ const [ downloading , setDownloading ] = useState ( false ) ;
50
+
51
+ const libraryAction = useCallback ( async ( ) => {
52
+ setAdding ( true ) ;
53
+ if ( libraryAgent ) {
54
+ toast ( {
55
+ description : "Redirecting to your library..." ,
56
+ duration : 2000 ,
57
+ } ) ;
58
+ // Redirect to the library agent page
59
+ router . push ( `/library/agents/${ libraryAgent . id } ` ) ;
60
+ return ;
61
+ }
48
62
try {
49
63
const newLibraryAgent = await api . addMarketplaceAgentToLibrary (
50
64
storeListingVersionId ,
51
65
) ;
52
66
completeStep ( "MARKETPLACE_ADD_AGENT" ) ;
53
67
router . push ( `/library/agents/${ newLibraryAgent . id } ` ) ;
68
+ toast ( {
69
+ title : "Agent Added" ,
70
+ description : "Redirecting to your library..." ,
71
+ duration : 2000 ,
72
+ } ) ;
54
73
} catch ( error ) {
55
74
console . error ( "Failed to add agent to library:" , error ) ;
75
+ toast ( {
76
+ title : "Error" ,
77
+ description : "Failed to add agent to library. Please try again." ,
78
+ variant : "destructive" ,
79
+ } ) ;
56
80
}
57
- } ;
81
+ } , [ toast , api , storeListingVersionId , completeStep , router ] ) ;
58
82
59
- const handleDownloadToLibrary = async ( ) => {
83
+ const handleDownload = useCallback ( async ( ) => {
60
84
const downloadAgent = async ( ) : Promise < void > => {
61
85
setDownloading ( true ) ;
62
86
try {
@@ -89,12 +113,16 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
89
113
} ) ;
90
114
} catch ( error ) {
91
115
console . error ( `Error downloading agent:` , error ) ;
92
- throw error ;
116
+ toast ( {
117
+ title : "Error" ,
118
+ description : "Failed to download agent. Please try again." ,
119
+ variant : "destructive" ,
120
+ } ) ;
93
121
}
94
122
} ;
95
123
await downloadAgent ( ) ;
96
124
setDownloading ( false ) ;
97
- } ;
125
+ } , [ setDownloading , api , storeListingVersionId , toast ] ) ;
98
126
99
127
return (
100
128
< div className = "w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0" >
@@ -105,65 +133,61 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
105
133
106
134
{ /* Creator */ }
107
135
< div className = "mb-3 flex w-full items-center gap-1.5 lg:mb-4" >
108
- < div className = "font-geist text-base font-normal text-neutral-800 dark:text-neutral-200 sm:text-lg lg:text-xl" >
136
+ < div className = "font-sans text-base font-normal text-neutral-800 dark:text-neutral-200 sm:text-lg lg:text-xl" >
109
137
by
110
138
</ div >
111
139
< Link
112
140
href = { `/marketplace/creator/${ encodeURIComponent ( creator ) } ` }
113
- className = "font-geist text-base font-medium text-neutral-800 hover:underline dark:text-neutral-200 sm:text-lg lg:text-xl"
141
+ className = "font-sans text-base font-medium text-neutral-800 hover:underline dark:text-neutral-200 sm:text-lg lg:text-xl"
114
142
>
115
143
{ creator }
116
144
</ Link >
117
145
</ div >
118
146
119
147
{ /* Short Description */ }
120
- < div className = "font-geist mb-4 line-clamp-2 w-full text-base font-normal leading-normal text-neutral-600 dark:text-neutral-300 sm:text-lg lg:mb-6 lg:text-xl lg:leading-7" >
148
+ < div className = "mb-4 line-clamp-2 w-full font-sans text-base font-normal leading-normal text-neutral-600 dark:text-neutral-300 sm:text-lg lg:mb-6 lg:text-xl lg:leading-7" >
121
149
{ shortDescription }
122
150
</ div >
123
151
124
- { /* Run Agent Button */ }
125
- < div className = "mb-4 w-full lg:mb-[60px]" >
126
- { user ? (
152
+ { /* Buttons */ }
153
+ < div className = "mb-4 flex w-full gap-3 lg:mb-[60px]" >
154
+ { user && (
127
155
< button
128
- onClick = { handleAddToLibrary }
129
- className = "inline-flex w-full items-center justify-center gap-2 rounded-[38px] bg-violet-600 px-4 py-3 transition-colors hover:bg-violet-700 sm:w-auto sm:gap-2.5 sm:px-5 sm:py-3.5 lg:px-6 lg:py-4"
130
- >
131
- < IconPlay className = "h-5 w-5 text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
132
- < span className = "font-poppins text-base font-medium text-neutral-50 sm:text-lg" >
133
- Add To Library
134
- </ span >
135
- </ button >
136
- ) : (
137
- < button
138
- onClick = { handleDownloadToLibrary }
139
- className = { `inline-flex w-full items-center justify-center gap-2 rounded-[38px] px-4 py-3 transition-colors sm:w-auto sm:gap-2.5 sm:px-5 sm:py-3.5 lg:px-6 lg:py-4 ${
140
- downloading
141
- ? "bg-neutral-400"
142
- : "bg-violet-600 hover:bg-violet-700"
143
- } `}
144
- disabled = { downloading }
145
- >
146
- { downloading ? (
147
- < LoaderIcon className = "h-5 w-5 animate-spin text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
148
- ) : (
149
- < DownloadIcon className = "h-5 w-5 text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
156
+ className = { cn (
157
+ "inline-flex min-w-24 items-center justify-center rounded-full bg-violet-600 px-4 py-3" ,
158
+ "transition-colors duration-200 hover:bg-violet-500 disabled:bg-zinc-400" ,
150
159
) }
151
- < span className = "font-poppins text-base font-medium text-neutral-50 sm:text-lg" >
152
- { downloading ? "Downloading..." : "Download Agent as File" }
160
+ onClick = { libraryAction }
161
+ disabled = { adding }
162
+ >
163
+ < span className = "justify-start font-sans text-sm font-medium leading-snug text-primary-foreground" >
164
+ { libraryAgent ? "See runs" : "Add to library" }
153
165
</ span >
154
166
</ button >
155
167
) }
168
+ < button
169
+ className = { cn (
170
+ "inline-flex min-w-24 items-center justify-center rounded-full bg-zinc-200 px-4 py-3" ,
171
+ "transition-colors duration-200 hover:bg-zinc-200/70 disabled:bg-zinc-200/40" ,
172
+ ) }
173
+ onClick = { handleDownload }
174
+ disabled = { downloading }
175
+ >
176
+ < div className = "justify-start text-center font-sans text-sm font-medium leading-snug text-zinc-800" >
177
+ Download agent
178
+ </ div >
179
+ </ button >
156
180
</ div >
157
181
158
182
{ /* Rating and Runs */ }
159
183
< div className = "mb-4 flex w-full items-center justify-between lg:mb-[44px]" >
160
184
< div className = "flex items-center gap-1.5 sm:gap-2" >
161
- < span className = "font-geist whitespace-nowrap text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg" >
185
+ < span className = "whitespace-nowrap font-sans text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg" >
162
186
{ rating . toFixed ( 1 ) }
163
187
</ span >
164
188
< div className = "flex gap-0.5" > { StarRatingIcons ( rating ) } </ div >
165
189
</ div >
166
- < div className = "font-geist whitespace-nowrap text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg" >
190
+ < div className = "whitespace-nowrap font-sans text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg" >
167
191
{ runs . toLocaleString ( ) } runs
168
192
</ div >
169
193
</ div >
@@ -183,14 +207,14 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
183
207
184
208
{ /* Categories */ }
185
209
< div className = "mb-4 flex w-full flex-col gap-1.5 sm:gap-2 lg:mb-[36px]" >
186
- < div className = "font-geist decoration-skip-ink-none mb-1.5 text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2" >
210
+ < div className = "decoration-skip-ink-none mb-1.5 font-sans text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2" >
187
211
Categories
188
212
</ div >
189
213
< div className = "flex flex-wrap gap-1.5 sm:gap-2" >
190
214
{ categories . map ( ( category , index ) => (
191
215
< div
192
216
key = { index }
193
- className = "font-geist decoration-skip-ink-none whitespace-nowrap rounded-full border border-neutral-600 bg-white px-2 py-0.5 text-base font-normal leading-6 text-neutral-800 underline-offset-[from-font] dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-200 sm:px-[16px] sm:py-[10px]"
217
+ className = "decoration-skip-ink-none whitespace-nowrap rounded-full border border-neutral-600 bg-white px-2 py-0.5 font-sans text-base font-normal leading-6 text-neutral-800 underline-offset-[from-font] dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-200 sm:px-[16px] sm:py-[10px]"
194
218
>
195
219
{ category }
196
220
</ div >
@@ -200,10 +224,10 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
200
224
201
225
{ /* Version History */ }
202
226
< div className = "flex w-full flex-col gap-0.5 sm:gap-1" >
203
- < div className = "font-geist decoration-skip-ink-none mb-1.5 text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2" >
227
+ < div className = "decoration-skip-ink-none mb-1.5 font-sans text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2" >
204
228
Version history
205
229
</ div >
206
- < div className = "font-geist decoration-skip-ink-none text-base font-normal leading-6 text-neutral-600 underline-offset-[from-font] dark:text-neutral-400" >
230
+ < div className = "decoration-skip-ink-none font-sans text-base font-normal leading-6 text-neutral-600 underline-offset-[from-font] dark:text-neutral-400" >
207
231
Last updated { lastUpdated }
208
232
</ div >
209
233
< div className = "text-xs text-neutral-600 dark:text-neutral-400 sm:text-sm" >
0 commit comments