Compare commits
100 Commits
claude-cod
...
abhi/block
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f757d5cf4 | ||
|
|
783f18133e | ||
|
|
27e53aa3dd | ||
|
|
a24673d15f | ||
|
|
9d2d9606e8 | ||
|
|
91407dfc33 | ||
|
|
851919d2d5 | ||
|
|
d6acb02cb6 | ||
|
|
9c07206725 | ||
|
|
4bd3447301 | ||
|
|
8adc9f967d | ||
|
|
349b70c4bc | ||
|
|
9ecfa1e1f1 | ||
|
|
4e17f9c49e | ||
|
|
31fdeeb706 | ||
|
|
e42b24c029 | ||
|
|
2d52a57a21 | ||
|
|
f45123f6b6 | ||
|
|
d524518f41 | ||
|
|
81d1b28d92 | ||
|
|
4e4e754ac1 | ||
|
|
e409d7aa34 | ||
|
|
8312a339c2 | ||
|
|
5b45d246ef | ||
|
|
5c7c7ca874 | ||
|
|
c93c5e35ba | ||
|
|
ce989b1bf7 | ||
|
|
c1c919b88b | ||
|
|
21a91fe9fd | ||
|
|
b2f3d8c1f2 | ||
|
|
46ab2e3b20 | ||
|
|
5b40700299 | ||
|
|
1a97020eeb | ||
|
|
39d03f2090 | ||
|
|
8088d294f4 | ||
|
|
31266949ed | ||
|
|
f4eb00a6ad | ||
|
|
f75cc0dd11 | ||
|
|
21b612625f | ||
|
|
eec0d276d5 | ||
|
|
c6941e7f6e | ||
|
|
325684a10f | ||
|
|
cf057cbbda | ||
|
|
f3a7be1fd3 | ||
|
|
97bcb0f95e | ||
|
|
dd71d65706 | ||
|
|
2b2d26bcde | ||
|
|
67f6f43e1b | ||
|
|
a3409c9578 | ||
|
|
7f82457ea4 | ||
|
|
a5c0fabc00 | ||
|
|
09dba93a4a | ||
|
|
ea2cd3e7bf | ||
|
|
d3d0ccf732 | ||
|
|
d8d5d6ec0c | ||
|
|
f45b09c0b5 | ||
|
|
1e89b6d3a4 | ||
|
|
950a85e179 | ||
|
|
c5e3148145 | ||
|
|
a135ba3f0b | ||
|
|
fe95e27226 | ||
|
|
711ca10cc9 | ||
|
|
1346d8230c | ||
|
|
07c84a4757 | ||
|
|
596824c1e7 | ||
|
|
79afa6db99 | ||
|
|
e034c16f31 | ||
|
|
9012eff1ac | ||
|
|
0361ea4aa4 | ||
|
|
6f1c522ea3 | ||
|
|
2d654bf64b | ||
|
|
bb69e32fee | ||
|
|
1be830835b | ||
|
|
a2a4d546f7 | ||
|
|
3053a7bd06 | ||
|
|
bbf4108136 | ||
|
|
95387bcf78 | ||
|
|
e1fc56e6f3 | ||
|
|
2a06956802 | ||
|
|
32231ff80f | ||
|
|
d0b23c085f | ||
|
|
e718d3d3d8 | ||
|
|
1971a62684 | ||
|
|
e125b5923c | ||
|
|
c6942e4e6f | ||
|
|
c9e421a219 | ||
|
|
7868373897 | ||
|
|
f1c8399e0e | ||
|
|
97ba69ef1c | ||
|
|
773e1488bf | ||
|
|
4273be59ba | ||
|
|
06e524788a | ||
|
|
bc08012771 | ||
|
|
4af0aedebd | ||
|
|
d22464a75e | ||
|
|
82e3a485f0 | ||
|
|
8165ad5879 | ||
|
|
451284de76 | ||
|
|
1d8c7c5e1a | ||
|
|
34be6a3379 |
@@ -81,9 +81,12 @@
|
||||
"react-markdown": "9.0.3",
|
||||
"react-modal": "3.16.3",
|
||||
"react-shepherd": "6.1.8",
|
||||
"react-timeago": "^8.2.0",
|
||||
"recharts": "2.15.3",
|
||||
"shepherd.js": "14.5.0",
|
||||
"tailwind-merge": "2.6.0",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwind-scrollbar-hide": "^2.0.0",
|
||||
"tailwindcss-animate": "1.0.7",
|
||||
"uuid": "11.1.0",
|
||||
"zod": "3.25.51"
|
||||
|
||||
56
autogpt_platform/frontend/pnpm-lock.yaml
generated
@@ -179,6 +179,9 @@ importers:
|
||||
react-shepherd:
|
||||
specifier: 6.1.8
|
||||
version: 6.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)
|
||||
react-timeago:
|
||||
specifier: ^8.2.0
|
||||
version: 8.2.0(react@18.3.1)
|
||||
recharts:
|
||||
specifier: 2.15.3
|
||||
version: 2.15.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -188,6 +191,12 @@ importers:
|
||||
tailwind-merge:
|
||||
specifier: 2.6.0
|
||||
version: 2.6.0
|
||||
tailwind-scrollbar:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2(react@18.3.1)(tailwindcss@3.4.17)
|
||||
tailwind-scrollbar-hide:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(tailwindcss@3.4.17)
|
||||
tailwindcss-animate:
|
||||
specifier: 1.0.7
|
||||
version: 1.0.7(tailwindcss@3.4.17)
|
||||
@@ -3138,6 +3147,9 @@ packages:
|
||||
'@types/phoenix@1.6.6':
|
||||
resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==}
|
||||
|
||||
'@types/prismjs@1.26.5':
|
||||
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
||||
|
||||
'@types/prop-types@15.7.14':
|
||||
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
|
||||
|
||||
@@ -6435,6 +6447,11 @@ packages:
|
||||
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
prism-react-renderer@2.4.1:
|
||||
resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
|
||||
peerDependencies:
|
||||
react: '>=16.0.0'
|
||||
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
@@ -6620,6 +6637,11 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-timeago@8.2.0:
|
||||
resolution: {integrity: sha512-RWDlG3Jj+iwv+yNEDweA/Qk1mxE8i/Oc4oW8Irp29ZfBp+eNpqqYPMLPYQJyfRMJcGB8CmWkEGMYhB4fW8eZlQ==}
|
||||
peerDependencies:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
react-transition-group@4.4.5:
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
@@ -7171,6 +7193,17 @@ packages:
|
||||
tailwind-merge@2.6.0:
|
||||
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
|
||||
|
||||
tailwind-scrollbar-hide@2.0.0:
|
||||
resolution: {integrity: sha512-lqiIutHliEiODwBRHy4G2+Tcayo2U7+3+4frBmoMETD72qtah+XhOk5XcPzC1nJvXhXUdfl2ajlMhUc2qC6CIg==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20'
|
||||
|
||||
tailwind-scrollbar@4.0.2:
|
||||
resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
peerDependencies:
|
||||
tailwindcss: 4.x
|
||||
|
||||
tailwindcss-animate@1.0.7:
|
||||
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
|
||||
peerDependencies:
|
||||
@@ -10959,6 +10992,8 @@ snapshots:
|
||||
|
||||
'@types/phoenix@1.6.6': {}
|
||||
|
||||
'@types/prismjs@1.26.5': {}
|
||||
|
||||
'@types/prop-types@15.7.14': {}
|
||||
|
||||
'@types/react-dom@18.3.5(@types/react@18.3.17)':
|
||||
@@ -14925,6 +14960,12 @@ snapshots:
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 18.3.1
|
||||
|
||||
prism-react-renderer@2.4.1(react@18.3.1):
|
||||
dependencies:
|
||||
'@types/prismjs': 1.26.5
|
||||
clsx: 2.1.1
|
||||
react: 18.3.1
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
process-on-spawn@1.1.0:
|
||||
@@ -15124,6 +15165,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.17
|
||||
|
||||
react-timeago@8.2.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
@@ -15838,6 +15883,17 @@ snapshots:
|
||||
|
||||
tailwind-merge@2.6.0: {}
|
||||
|
||||
tailwind-scrollbar-hide@2.0.0(tailwindcss@3.4.17):
|
||||
dependencies:
|
||||
tailwindcss: 3.4.17
|
||||
|
||||
tailwind-scrollbar@4.0.2(react@18.3.1)(tailwindcss@3.4.17):
|
||||
dependencies:
|
||||
prism-react-renderer: 2.4.1(react@18.3.1)
|
||||
tailwindcss: 3.4.17
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
|
||||
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
|
||||
dependencies:
|
||||
tailwindcss: 3.4.17
|
||||
|
||||
BIN
autogpt_platform/frontend/public/integrations/anthropic.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
autogpt_platform/frontend/public/integrations/apollo.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
autogpt_platform/frontend/public/integrations/d_id.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
autogpt_platform/frontend/public/integrations/e2b.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
autogpt_platform/frontend/public/integrations/exa.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
autogpt_platform/frontend/public/integrations/fal.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
autogpt_platform/frontend/public/integrations/google_maps.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
autogpt_platform/frontend/public/integrations/groq.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
autogpt_platform/frontend/public/integrations/ideogram.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
autogpt_platform/frontend/public/integrations/jina.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
autogpt_platform/frontend/public/integrations/llama_api.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
autogpt_platform/frontend/public/integrations/nvidia.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
autogpt_platform/frontend/public/integrations/ollama.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
autogpt_platform/frontend/public/integrations/open_router.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
autogpt_platform/frontend/public/integrations/openai.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
autogpt_platform/frontend/public/integrations/replicate.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
autogpt_platform/frontend/public/integrations/revid.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
autogpt_platform/frontend/public/integrations/screenshotone.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
autogpt_platform/frontend/public/integrations/slant3d.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
autogpt_platform/frontend/public/integrations/smartlead.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
autogpt_platform/frontend/public/integrations/twitter.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
autogpt_platform/frontend/public/integrations/unreal_speech.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
autogpt_platform/frontend/public/integrations/zerobounce.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,74 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { beautifyString, cn } from "@/lib/utils";
|
||||
import { Plus } from "lucide-react";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
import { highlightText } from "./IntegrationBlock";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
title?: string;
|
||||
description?: string;
|
||||
highlightedText?: string;
|
||||
}
|
||||
|
||||
interface BlockComponent extends React.FC<Props> {
|
||||
Skeleton: React.FC<{ className?: string }>;
|
||||
}
|
||||
|
||||
const Block: BlockComponent = ({
|
||||
title,
|
||||
description,
|
||||
highlightedText,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"group flex h-16 w-full min-w-[7.5rem] items-center justify-start space-x-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 px-[0.875rem] py-[0.625rem] text-start shadow-none",
|
||||
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:pointer-events-none",
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<span
|
||||
className={cn(
|
||||
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
{title && highlightText(beautifyString(title), highlightedText)}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
{description && highlightText(description, highlightedText)}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-7 w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
|
||||
)}
|
||||
>
|
||||
<Plus className="h-5 w-5 text-zinc-50" strokeWidth={2} />
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const BlockSkeleton = () => {
|
||||
return (
|
||||
<Skeleton className="flex h-16 w-full min-w-[7.5rem] animate-pulse items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-100 px-[0.875rem] py-[0.625rem]">
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
|
||||
<Skeleton className="h-5 w-32 rounded bg-zinc-200" />
|
||||
</div>
|
||||
<Skeleton className="h-7 w-7 rounded-[0.5rem] bg-zinc-200" />
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
Block.Skeleton = BlockSkeleton;
|
||||
|
||||
export default Block;
|
||||
@@ -0,0 +1,35 @@
|
||||
// BLOCK MENU TODO: We need a disable state in this, currently it's not in design.
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
selected?: boolean;
|
||||
children?: React.ReactNode; // For icon purpose
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const ControlPanelButton: React.FC<Props> = ({
|
||||
selected = false,
|
||||
children,
|
||||
disabled,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
// Using div instead of button, because it's only for design purposes. We are using this to give design to PopoverTrigger.
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-[4.25rem] w-[4.25rem] items-center justify-center whitespace-normal bg-white p-[1.38rem] text-zinc-800 shadow-none hover:cursor-pointer hover:bg-zinc-100 hover:text-zinc-950 focus:ring-0",
|
||||
selected &&
|
||||
"bg-violet-50 text-violet-700 hover:cursor-default hover:bg-violet-50 hover:text-violet-700 active:bg-violet-50 active:text-violet-700",
|
||||
disabled && className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ControlPanelButton;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AlertCircle, RefreshCw } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
interface ErrorStateProps {
|
||||
title?: string;
|
||||
message?: string;
|
||||
error?: string | Error | null;
|
||||
onRetry?: () => void;
|
||||
retryLabel?: string;
|
||||
className?: string;
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
const ErrorState: React.FC<ErrorStateProps> = ({
|
||||
title = "Something went wrong",
|
||||
message,
|
||||
error,
|
||||
onRetry,
|
||||
retryLabel = "Retry",
|
||||
className,
|
||||
showIcon = true,
|
||||
}) => {
|
||||
const errorMessage = error
|
||||
? error instanceof Error
|
||||
? error.message
|
||||
: String(error)
|
||||
: message || "An unexpected error occurred. Please try again.";
|
||||
|
||||
const classes =
|
||||
"flex h-full w-full flex-col items-center justify-center text-center space-y-4";
|
||||
|
||||
return (
|
||||
<div className={cn(classes, className)}>
|
||||
{showIcon && <AlertCircle className="h-12 w-12" strokeWidth={1.5} />}
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-zinc-800">{title}</p>
|
||||
<p className="text-sm text-zinc-600">{errorMessage}</p>
|
||||
</div>
|
||||
|
||||
{onRetry && (
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={onRetry}
|
||||
className="mt-2 h-7 bg-zinc-800 text-xs"
|
||||
>
|
||||
<RefreshCw className="mr-1 h-3 w-3" />
|
||||
{retryLabel}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorState;
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { X } from "lucide-react";
|
||||
import React, { ButtonHTMLAttributes, useState } from "react";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
selected?: boolean;
|
||||
number?: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
const FilterChip: React.FC<Props> = ({
|
||||
selected = false,
|
||||
number,
|
||||
name,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"group w-fit space-x-1 rounded-[1.5rem] border border-zinc-300 bg-transparent px-[0.625rem] py-[0.375rem] shadow-none transition-transform duration-300 ease-in-out",
|
||||
"hover:border-violet-500 hover:bg-transparent focus:ring-0 disabled:pointer-events-none",
|
||||
selected && "border-0 bg-violet-700 hover:border",
|
||||
)}
|
||||
{...rest}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"font-sans text-sm font-medium leading-[1.375rem] text-zinc-600 group-hover:text-zinc-600 group-disabled:text-zinc-400",
|
||||
selected && "text-zinc-50",
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
{selected &&
|
||||
(isHovering && number !== undefined ? (
|
||||
<span className="flex h-[1.375rem] items-center rounded-[1.25rem] bg-violet-700 p-[0.375rem] text-zinc-50 transition-all duration-300 ease-in-out animate-in fade-in zoom-in">
|
||||
{number > 100 ? "100+" : number}
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex h-4 w-4 items-center justify-center rounded-full bg-zinc-50 transition-all duration-300 ease-in-out">
|
||||
<X
|
||||
className="h-3 w-3 rounded-full text-violet-700"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterChip;
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { beautifyString, cn } from "@/lib/utils";
|
||||
import Image from "next/image";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon_url?: string;
|
||||
number_of_blocks?: number;
|
||||
}
|
||||
|
||||
interface IntegrationComponent extends React.FC<Props> {
|
||||
Skeleton: React.FC<{ className?: string }>;
|
||||
}
|
||||
|
||||
const Integration: IntegrationComponent = ({
|
||||
title,
|
||||
icon_url,
|
||||
description,
|
||||
className,
|
||||
number_of_blocks,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"group flex h-16 w-full min-w-[7.5rem] items-center justify-start space-x-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 px-[0.875rem] py-[0.625rem] text-start shadow-none",
|
||||
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-50 active:ring-1 active:ring-zinc-300 disabled:pointer-events-none",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<div className="relative h-[2.625rem] w-[2.625rem] overflow-hidden rounded-[0.5rem] bg-white">
|
||||
{icon_url && (
|
||||
<Image
|
||||
src={icon_url}
|
||||
alt="integration-icon"
|
||||
fill
|
||||
sizes="2.25rem"
|
||||
className="w-full rounded-[0.5rem] object-contain group-disabled:opacity-50"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="line-clamp-1 flex-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-700 group-disabled:text-zinc-400">
|
||||
{title && beautifyString(title)}
|
||||
</p>
|
||||
<span className="flex h-[1.375rem] w-[1.6875rem] items-center justify-center rounded-[1.25rem] bg-[#f0f0f0] p-1.5 font-sans text-sm leading-[1.375rem] text-zinc-500 group-disabled:text-zinc-400">
|
||||
{number_of_blocks}
|
||||
</span>
|
||||
</div>
|
||||
<span className="line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400">
|
||||
{description}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const IntegrationSkeleton: React.FC<{ className?: string }> = ({
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<Skeleton
|
||||
className={cn(
|
||||
"flex h-16 w-full min-w-[7.5rem] animate-pulse items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-100 px-[0.875rem] py-[0.625rem]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Skeleton className="h-[2.625rem] w-[2.625rem] rounded-[0.5rem] bg-zinc-200" />
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
|
||||
<Skeleton className="h-[1.375rem] w-[1.6875rem] rounded-[1.25rem] bg-zinc-200" />
|
||||
</div>
|
||||
<Skeleton className="h-5 w-[80%] rounded bg-zinc-200" />
|
||||
</div>
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
Integration.Skeleton = IntegrationSkeleton;
|
||||
|
||||
export default Integration;
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { beautifyString, cn } from "@/lib/utils";
|
||||
import { Plus } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon_url?: string;
|
||||
highlightedText?: string;
|
||||
}
|
||||
|
||||
interface IntegrationBlockComponent extends React.FC<Props> {
|
||||
Skeleton: React.FC<{ className?: string }>;
|
||||
}
|
||||
|
||||
export const highlightText = (
|
||||
text: string | undefined,
|
||||
highlight: string | undefined,
|
||||
) => {
|
||||
if (!text || !highlight) return text;
|
||||
const parts = text.split(new RegExp(`(${highlight})`, "gi"));
|
||||
return parts.map((part, i) =>
|
||||
part.toLowerCase() === highlight?.toLowerCase() ? (
|
||||
<mark key={i} className="bg-transparent font-bold">
|
||||
{part}
|
||||
</mark>
|
||||
) : (
|
||||
part
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const IntegrationBlock: IntegrationBlockComponent = ({
|
||||
title,
|
||||
icon_url,
|
||||
description,
|
||||
className,
|
||||
highlightedText,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"group flex h-16 w-full min-w-[7.5rem] items-center justify-start gap-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 px-[0.875rem] py-[0.625rem] text-start shadow-none",
|
||||
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:pointer-events-none",
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<div className="relative h-[2.625rem] w-[2.625rem] rounded-[0.5rem] bg-white">
|
||||
{icon_url && (
|
||||
<Image
|
||||
src={icon_url}
|
||||
alt="integration-icon"
|
||||
fill
|
||||
sizes="2.25rem"
|
||||
className="w-full object-contain group-disabled:opacity-50"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<span
|
||||
className={cn(
|
||||
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
{title && highlightText(beautifyString(title), highlightedText)}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
{description && highlightText(description, highlightedText)}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-7 w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
|
||||
)}
|
||||
>
|
||||
<Plus className="h-5 w-5 text-zinc-50" strokeWidth={2} />
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const IntegrationBlockSkeleton = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<Skeleton
|
||||
className={cn(
|
||||
"flex h-16 w-full min-w-[7.5rem] animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 px-[0.875rem] py-[0.625rem]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Skeleton className="h-[2.625rem] w-[2.625rem] rounded-[0.5rem] bg-zinc-200" />
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
|
||||
<Skeleton className="h-5 w-32 rounded bg-zinc-200" />
|
||||
</div>
|
||||
<Skeleton className="h-7 w-7 rounded-[0.5rem] bg-zinc-200" />
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
IntegrationBlock.Skeleton = IntegrationBlockSkeleton;
|
||||
|
||||
export default IntegrationBlock;
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { beautifyString, cn } from "@/lib/utils";
|
||||
import Image from "next/image";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
name?: string;
|
||||
icon_url?: string;
|
||||
}
|
||||
|
||||
interface IntegrationChipComponent extends React.FC<Props> {
|
||||
Skeleton: React.FC;
|
||||
}
|
||||
|
||||
const IntegrationChip: IntegrationChipComponent = ({
|
||||
icon_url,
|
||||
name,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"flex h-[3.25rem] w-full min-w-[7.5rem] justify-start gap-2 whitespace-normal rounded-[0.5rem] bg-zinc-50 p-2 pr-3 shadow-none",
|
||||
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<div className="relative h-9 w-9 rounded-[0.5rem] bg-transparent">
|
||||
{icon_url && (
|
||||
<Image
|
||||
src={icon_url}
|
||||
alt="integration-icon"
|
||||
fill
|
||||
sizes="2.25rem"
|
||||
className="w-full object-contain"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className="truncate font-sans text-sm font-normal leading-[1.375rem] text-zinc-800">
|
||||
{name && beautifyString(name)}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const IntegrationChipSkeleton: React.FC = () => {
|
||||
return (
|
||||
<Skeleton className="flex h-[3.25rem] w-full min-w-[7.5rem] gap-2 rounded-[0.5rem] bg-zinc-100 p-2 pr-3">
|
||||
<Skeleton className="h-9 w-12 rounded-[0.5rem] bg-zinc-200" />
|
||||
<Skeleton className="h-5 w-24 self-center rounded-sm bg-zinc-200" />
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
IntegrationChip.Skeleton = IntegrationChipSkeleton;
|
||||
|
||||
export default IntegrationChip;
|
||||
@@ -0,0 +1,135 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ExternalLink, Loader2, Plus } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
import { highlightText } from "./IntegrationBlock";
|
||||
import Link from "next/link";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
title?: string;
|
||||
creator_name?: string;
|
||||
number_of_runs?: number;
|
||||
image_url?: string;
|
||||
highlightedText?: string;
|
||||
slug: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface MarketplaceAgentBlockComponent extends React.FC<Props> {
|
||||
Skeleton: React.FC<{ className?: string }>;
|
||||
}
|
||||
|
||||
const MarketplaceAgentBlock: MarketplaceAgentBlockComponent = ({
|
||||
title,
|
||||
image_url,
|
||||
creator_name,
|
||||
number_of_runs,
|
||||
className,
|
||||
loading,
|
||||
highlightedText,
|
||||
slug,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"group flex h-[4.375rem] w-full min-w-[7.5rem] items-center justify-start gap-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 p-[0.625rem] pr-[0.875rem] text-start shadow-none",
|
||||
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:pointer-events-none",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<div className="relative h-[3.125rem] w-[5.625rem] overflow-hidden rounded-[0.375rem] bg-white">
|
||||
{image_url && (
|
||||
<Image
|
||||
src={image_url}
|
||||
alt="integration-icon"
|
||||
fill
|
||||
sizes="5.625rem"
|
||||
className="w-full object-contain group-disabled:opacity-50"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<span
|
||||
className={cn(
|
||||
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
{highlightText(title, highlightedText)}
|
||||
</span>
|
||||
<div className="flex items-center space-x-2.5">
|
||||
<span
|
||||
className={cn(
|
||||
"truncate font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
By {creator_name}
|
||||
</span>
|
||||
|
||||
<span className="font-sans text-zinc-400">•</span>
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
"truncate font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
{number_of_runs} runs
|
||||
</span>
|
||||
<span className="font-sans text-zinc-400">•</span>
|
||||
<Link
|
||||
href={`/marketplace/agent/${creator_name}/${slug}`}
|
||||
className="flex gap-0.5 truncate"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="font-sans text-xs leading-5 text-blue-700 underline">
|
||||
Agent page
|
||||
</span>
|
||||
<ExternalLink className="h-4 w-4 text-blue-700" strokeWidth={1} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-7 min-w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
|
||||
)}
|
||||
>
|
||||
{!loading ? (
|
||||
<Plus className="h-5 w-5 text-zinc-50" strokeWidth={2} />
|
||||
) : (
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const MarketplaceAgentBlockSkeleton: React.FC<{ className?: string }> = ({
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<Skeleton
|
||||
className={cn(
|
||||
"flex h-[4.375rem] w-full min-w-[7.5rem] animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 p-[0.625rem] pr-[0.875rem]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Skeleton className="h-[3.125rem] w-[5.625rem] rounded-[0.375rem] bg-zinc-200" />
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
|
||||
<div className="flex items-center gap-1">
|
||||
<Skeleton className="h-5 w-16 rounded bg-zinc-200" />
|
||||
|
||||
<Skeleton className="h-5 w-16 rounded bg-zinc-200" />
|
||||
</div>
|
||||
</div>
|
||||
<Skeleton className="h-7 w-7 rounded-[0.5rem] bg-zinc-200" />
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
MarketplaceAgentBlock.Skeleton = MarketplaceAgentBlockSkeleton;
|
||||
|
||||
export default MarketplaceAgentBlock;
|
||||
@@ -0,0 +1,42 @@
|
||||
// BLOCK MENU TODO: We need to add a better hover state to it; currently it's not in the design either.
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
selected?: boolean;
|
||||
number?: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
const MenuItem: React.FC<Props> = ({
|
||||
selected = false,
|
||||
number,
|
||||
name,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"flex h-[2.375rem] w-[12.875rem] justify-between whitespace-normal rounded-[0.5rem] bg-transparent p-2 pl-3 shadow-none",
|
||||
"hover:cursor-default hover:bg-zinc-100 focus:ring-0",
|
||||
selected && "bg-zinc-100",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<span className="truncate font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
|
||||
{name}
|
||||
</span>
|
||||
{number && (
|
||||
<span className="font-sans text-sm font-normal leading-[1.375rem] text-zinc-600">
|
||||
{number > 100 ? "100+" : number}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuItem;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
content?: string;
|
||||
}
|
||||
|
||||
interface SearchHistoryChipComponent extends React.FC<Props> {
|
||||
Skeleton: React.FC<{ className?: string }>;
|
||||
}
|
||||
|
||||
const SearchHistoryChip: SearchHistoryChipComponent = ({
|
||||
content,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"my-[1px] h-[2.25rem] space-x-1 rounded-[1.5rem] bg-zinc-50 p-[0.375rem] pr-[0.625rem] shadow-none",
|
||||
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<ArrowUpRight className="h-6 w-6 text-zinc-500" strokeWidth={1.25} />
|
||||
<span className="font-sans text-sm font-normal leading-[1.375rem] text-zinc-800">
|
||||
{content}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchHistoryChipSkeleton: React.FC<{ className?: string }> = ({
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<Skeleton
|
||||
className={cn("h-[2.25rem] w-32 rounded-[1.5rem] bg-zinc-100", className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
SearchHistoryChip.Skeleton = SearchHistoryChipSkeleton;
|
||||
|
||||
export default SearchHistoryChip;
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Plus } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
import { highlightText } from "./IntegrationBlock";
|
||||
import TimeAgo from "react-timeago";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
title?: string;
|
||||
edited_time?: Date;
|
||||
version?: number;
|
||||
image_url?: string;
|
||||
highlightedText?: string;
|
||||
}
|
||||
|
||||
interface UGCAgentBlockComponent extends React.FC<Props> {
|
||||
Skeleton: React.FC<{ className?: string }>;
|
||||
}
|
||||
|
||||
const UGCAgentBlock: UGCAgentBlockComponent = ({
|
||||
title,
|
||||
image_url,
|
||||
edited_time,
|
||||
version,
|
||||
className,
|
||||
highlightedText,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"group flex h-[4.375rem] w-full min-w-[7.5rem] items-center justify-start gap-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 p-[0.625rem] pr-[0.875rem] text-start shadow-none",
|
||||
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:pointer-events-none",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<div className="relative h-[3.125rem] w-[5.625rem] overflow-hidden rounded-[0.375rem] bg-white">
|
||||
{image_url && (
|
||||
<Image
|
||||
src={image_url}
|
||||
alt="integration-icon"
|
||||
fill
|
||||
sizes="5.625rem"
|
||||
className="w-full object-contain group-disabled:opacity-50"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<span
|
||||
className={cn(
|
||||
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
{highlightText(title, highlightedText)}
|
||||
</span>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<span
|
||||
className={cn(
|
||||
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
Edited {edited_time && <TimeAgo date={edited_time} />}
|
||||
</span>
|
||||
|
||||
<span className="font-sans text-zinc-400">•</span>
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
|
||||
)}
|
||||
>
|
||||
Version {version}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-7 w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
|
||||
)}
|
||||
>
|
||||
<Plus className="h-5 w-5 text-zinc-50" strokeWidth={2} />
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const UGCAgentBlockSkeleton: React.FC<{ className?: string }> = ({
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<Skeleton
|
||||
className={cn(
|
||||
"flex h-[4.375rem] w-full min-w-[7.5rem] animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 p-[0.625rem] pr-[0.875rem]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Skeleton className="h-[3.125rem] w-[5.625rem] rounded-[0.375rem] bg-zinc-200" />
|
||||
<div className="flex flex-1 flex-col items-start gap-0.5">
|
||||
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
|
||||
<div className="flex items-center gap-1">
|
||||
<Skeleton className="h-5 w-16 rounded bg-zinc-200" />
|
||||
<Skeleton className="h-5 w-16 rounded bg-zinc-200" />
|
||||
</div>
|
||||
</div>
|
||||
<Skeleton className="h-7 w-7 rounded-[0.5rem] bg-zinc-200" />
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
UGCAgentBlock.Skeleton = UGCAgentBlockSkeleton;
|
||||
|
||||
export default UGCAgentBlock;
|
||||
@@ -5,6 +5,7 @@ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { CheckIcon } from "@radix-ui/react-icons";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Check } from "lucide-react";
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
@@ -13,7 +14,7 @@ const Checkbox = React.forwardRef<
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-neutral-200 border-neutral-900 shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=checked]:text-neutral-50 dark:border-neutral-50 dark:border-neutral-800 dark:focus-visible:ring-neutral-300 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=checked]:text-neutral-900",
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-neutral-900 shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=checked]:text-neutral-50 dark:border-neutral-50 dark:border-neutral-800 dark:focus-visible:ring-neutral-300 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=checked]:text-neutral-900",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -21,7 +22,7 @@ const Checkbox = React.forwardRef<
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
<CheckIcon className="h-4 w-4" strokeWidth={2} />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
|
||||
@@ -256,7 +256,7 @@ const MultiSelectorList = forwardRef<
|
||||
<CommandList
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"scrollbar-thin scrollbar-track-transparent scrollbar-thumb-muted-foreground dark:scrollbar-thumb-muted scrollbar-thumb-rounded-lg absolute top-0 z-10 flex w-full flex-col gap-2 rounded-md border border-muted bg-background p-2 shadow-md transition-colors",
|
||||
"scrollbar-thumb-rounded-lg absolute top-0 z-10 flex w-full flex-col gap-2 rounded-md border border-muted bg-background p-2 shadow-md transition-colors scrollbar-thin scrollbar-track-transparent scrollbar-thumb-muted-foreground dark:scrollbar-thumb-muted",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
1
autogpt_platform/frontend/src/hooks/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { usePagination } from "./usePagination";
|
||||
232
autogpt_platform/frontend/src/hooks/usePagination.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import {
|
||||
Block,
|
||||
BlockRequest,
|
||||
Provider,
|
||||
StoreAgent,
|
||||
LibraryAgent,
|
||||
LibraryAgentSortEnum,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
|
||||
type BlocksPaginationRequest = { apiType: "blocks" } & BlockRequest;
|
||||
type ProvidersPaginationRequest = { apiType: "providers" } & {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
};
|
||||
type StoreAgentsPaginationRequest = { apiType: "store-agents" } & {
|
||||
featured?: boolean;
|
||||
creator?: string;
|
||||
sorted_by?: string;
|
||||
search_query?: string;
|
||||
category?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
};
|
||||
type LibraryAgentsPaginationRequest = { apiType: "library-agents" } & {
|
||||
search_term?: string;
|
||||
sort_by?: LibraryAgentSortEnum;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
};
|
||||
|
||||
type PaginationRequest =
|
||||
| BlocksPaginationRequest
|
||||
| ProvidersPaginationRequest
|
||||
| StoreAgentsPaginationRequest
|
||||
| LibraryAgentsPaginationRequest;
|
||||
|
||||
interface UsePaginationOptions<T extends PaginationRequest> {
|
||||
request: T;
|
||||
pageSize?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
interface UsePaginationReturn<T> {
|
||||
data: T[];
|
||||
loading: boolean;
|
||||
loadingMore: boolean;
|
||||
hasMore: boolean;
|
||||
error: string | null;
|
||||
scrollRef: React.RefObject<HTMLDivElement>;
|
||||
refresh: () => void;
|
||||
loadMore: () => void;
|
||||
}
|
||||
|
||||
type GetReturnType<T> = T extends BlocksPaginationRequest
|
||||
? Block
|
||||
: T extends ProvidersPaginationRequest
|
||||
? Provider
|
||||
: T extends StoreAgentsPaginationRequest
|
||||
? StoreAgent
|
||||
: T extends LibraryAgentsPaginationRequest
|
||||
? LibraryAgent
|
||||
: never;
|
||||
|
||||
export const usePagination = <T extends PaginationRequest>({
|
||||
request,
|
||||
pageSize = 10,
|
||||
enabled = true, // to allow pagination or not
|
||||
}: UsePaginationOptions<T>): UsePaginationReturn<GetReturnType<T>> => {
|
||||
const [data, setData] = useState<GetReturnType<T>[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const isLoadingRef = useRef(false);
|
||||
const requestRef = useRef(request);
|
||||
const api = useBackendAPI();
|
||||
|
||||
// because we are using this pagination for multiple components
|
||||
requestRef.current = request;
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (page: number, isLoadMore = false) => {
|
||||
if (isLoadingRef.current || !enabled) return;
|
||||
|
||||
isLoadingRef.current = true;
|
||||
|
||||
if (isLoadMore) {
|
||||
setLoadingMore(true);
|
||||
} else {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
let response;
|
||||
let newData: GetReturnType<T>[];
|
||||
let pagination;
|
||||
|
||||
const currentRequest = requestRef.current;
|
||||
const requestWithPagination = {
|
||||
...currentRequest,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
};
|
||||
|
||||
switch (currentRequest.apiType) {
|
||||
case "blocks":
|
||||
const { apiType: _, ...blockRequest } = requestWithPagination;
|
||||
response = await api.getBuilderBlocks(blockRequest);
|
||||
newData = response.blocks as GetReturnType<T>[];
|
||||
pagination = response.pagination;
|
||||
break;
|
||||
|
||||
case "providers":
|
||||
const { apiType: __, ...providerRequest } = requestWithPagination;
|
||||
response = await api.getProviders(providerRequest);
|
||||
newData = response.providers as GetReturnType<T>[];
|
||||
pagination = response.pagination;
|
||||
break;
|
||||
|
||||
case "store-agents":
|
||||
const { apiType: ___, ...storeAgentRequest } =
|
||||
requestWithPagination;
|
||||
response = await api.getStoreAgents(storeAgentRequest);
|
||||
newData = response.agents as GetReturnType<T>[];
|
||||
pagination = response.pagination;
|
||||
break;
|
||||
|
||||
case "library-agents":
|
||||
const { apiType: ____, ...libraryAgentRequest } =
|
||||
requestWithPagination;
|
||||
response = await api.listLibraryAgents(libraryAgentRequest);
|
||||
newData = response.agents as GetReturnType<T>[];
|
||||
pagination = response.pagination;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown request type: ${(currentRequest as any).apiType}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoadMore) {
|
||||
setData((prev) => [...prev, ...newData]);
|
||||
} else {
|
||||
setData(newData);
|
||||
}
|
||||
|
||||
setHasMore(page < pagination.total_pages);
|
||||
setCurrentPage(page);
|
||||
} catch (err) {
|
||||
const errorMessage =
|
||||
err instanceof Error ? err.message : "Failed to fetch data";
|
||||
setError(errorMessage);
|
||||
console.error("Error fetching data:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setLoadingMore(false);
|
||||
isLoadingRef.current = false;
|
||||
}
|
||||
},
|
||||
[api, pageSize, enabled],
|
||||
);
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
const scrollElement = scrollRef.current;
|
||||
if (
|
||||
!scrollElement ||
|
||||
loadingMore ||
|
||||
!hasMore ||
|
||||
isLoadingRef.current ||
|
||||
!enabled
|
||||
)
|
||||
return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = scrollElement;
|
||||
const threshold = 100;
|
||||
|
||||
if (scrollTop + clientHeight >= scrollHeight - threshold) {
|
||||
fetchData(currentPage + 1, true);
|
||||
}
|
||||
}, [fetchData, currentPage, loadingMore, hasMore, enabled]);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
setCurrentPage(1);
|
||||
setHasMore(true);
|
||||
setError(null);
|
||||
fetchData(1);
|
||||
}, [fetchData]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (!loadingMore && hasMore && !isLoadingRef.current && enabled) {
|
||||
fetchData(currentPage + 1, true);
|
||||
}
|
||||
}, [fetchData, currentPage, loadingMore, hasMore, enabled]);
|
||||
|
||||
const requestString = JSON.stringify(request);
|
||||
|
||||
useEffect(() => {
|
||||
if (enabled) {
|
||||
setCurrentPage(1);
|
||||
setHasMore(true);
|
||||
setError(null);
|
||||
setData([]);
|
||||
fetchData(1);
|
||||
}
|
||||
}, [requestString, enabled, fetchData]);
|
||||
|
||||
useEffect(() => {
|
||||
const scrollElement = scrollRef.current;
|
||||
if (scrollElement && enabled) {
|
||||
scrollElement.addEventListener("scroll", handleScroll);
|
||||
return () => scrollElement.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
}, [handleScroll, enabled]);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
loadingMore,
|
||||
hasMore,
|
||||
error,
|
||||
scrollRef,
|
||||
refresh,
|
||||
loadMore,
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,14 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Category, Graph } from "@/lib/autogpt-server-api/types";
|
||||
import {
|
||||
Block,
|
||||
BlockUIType,
|
||||
Category,
|
||||
Graph,
|
||||
LibraryAgent,
|
||||
SpecialBlockID,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { NodeDimension } from "@/components/Flow";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
@@ -396,3 +403,50 @@ export function getValue(key: string, value: any) {
|
||||
export function isEmptyOrWhitespace(str: string | undefined | null): boolean {
|
||||
return !str || str.trim().length === 0;
|
||||
}
|
||||
|
||||
export const convertLibraryAgentIntoBlock = (agent: LibraryAgent) => {
|
||||
const block = {
|
||||
id: SpecialBlockID.AGENT,
|
||||
name: agent.name,
|
||||
description:
|
||||
`Ver.${agent.graph_version}` +
|
||||
(agent.description ? ` | ${agent.description}` : ""),
|
||||
categories: [{ category: "AGENT", description: "" }],
|
||||
inputSchema: agent.input_schema,
|
||||
outputSchema: agent.output_schema,
|
||||
staticOutput: false,
|
||||
uiType: BlockUIType.AGENT,
|
||||
uiKey: agent.id,
|
||||
costs: [],
|
||||
hardcodedValues: {
|
||||
graph_id: agent.graph_id,
|
||||
graph_version: agent.graph_version,
|
||||
input_schema: agent.input_schema,
|
||||
output_schema: agent.output_schema,
|
||||
agent_name: agent.name,
|
||||
},
|
||||
} as Block;
|
||||
|
||||
return block;
|
||||
};
|
||||
|
||||
// Need to change it once, we got provider blocks
|
||||
export const getBlockType = (item: any) => {
|
||||
if (item?.inputSchema?.properties?.model?.title === "LLM Model") {
|
||||
return "ai_agent";
|
||||
}
|
||||
if (item.id && item.name && item.inputSchema && item.outputSchema) {
|
||||
return "block";
|
||||
}
|
||||
if (item.name && typeof item.integration_count === "number") {
|
||||
return "provider";
|
||||
}
|
||||
if (item.id && item.graph_id && item.status) {
|
||||
return "library_agent";
|
||||
}
|
||||
if (item.slug && item.agent_name && item.runs !== undefined) {
|
||||
return "store_agent";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,304 +1,304 @@
|
||||
// Note: all the comments with //(number)! are for the docs
|
||||
//ignore them when reading the code, but if you change something,
|
||||
//make sure to update the docs! Your autoformmater will break this page,
|
||||
// so don't run it on this file.
|
||||
// --8<-- [start:BuildPageExample]
|
||||
import { test } from "./fixtures";
|
||||
import { BuildPage } from "./pages/build.page";
|
||||
// // Note: all the comments with //(number)! are for the docs
|
||||
// //ignore them when reading the code, but if you change something,
|
||||
// //make sure to update the docs! Your autoformmater will break this page,
|
||||
// // so don't run it on this file.
|
||||
// // --8<-- [start:BuildPageExample]
|
||||
// import { test } from "./fixtures";
|
||||
// import { BuildPage } from "./pages/build.page";
|
||||
|
||||
// Reason Ignore: admonishment is in the wrong place visually with correct prettier rules
|
||||
// prettier-ignore
|
||||
test.describe("Build", () => { //(1)!
|
||||
let buildPage: BuildPage; //(2)!
|
||||
// // Reason Ignore: admonishment is in the wrong place visually with correct prettier rules
|
||||
// // prettier-ignore
|
||||
// test.describe("Build", () => { //(1)!
|
||||
// let buildPage: BuildPage; //(2)!
|
||||
|
||||
// Reason Ignore: admonishment is in the wrong place visually with correct prettier rules
|
||||
// prettier-ignore
|
||||
test.beforeEach(async ({ page, loginPage, testUser }, testInfo) => { //(3)! ts-ignore
|
||||
buildPage = new BuildPage(page);
|
||||
// // Reason Ignore: admonishment is in the wrong place visually with correct prettier rules
|
||||
// // prettier-ignore
|
||||
// test.beforeEach(async ({ page, loginPage, testUser }, testInfo) => { //(3)! ts-ignore
|
||||
// buildPage = new BuildPage(page);
|
||||
|
||||
// Start each test with login using worker auth
|
||||
await page.goto("/login"); //(4)!
|
||||
await loginPage.login(testUser.email, testUser.password);
|
||||
await test.expect(page).toHaveURL("/marketplace"); //(5)!
|
||||
await buildPage.navbar.clickBuildLink();
|
||||
});
|
||||
// // Start each test with login using worker auth
|
||||
// await page.goto("/login"); //(4)!
|
||||
// await loginPage.login(testUser.email, testUser.password);
|
||||
// await test.expect(page).toHaveURL("/marketplace"); //(5)!
|
||||
// await buildPage.navbar.clickBuildLink();
|
||||
// });
|
||||
|
||||
// Reason Ignore: admonishment is in the wrong place visually with correct prettier rules
|
||||
// prettier-ignore
|
||||
test("user can add a block", async ({ page }) => { //(6)!
|
||||
// workaround for #8788
|
||||
await buildPage.navbar.clickBuildLink();
|
||||
await test.expect(page).toHaveURL(new RegExp("/build"));
|
||||
await buildPage.waitForPageLoad();
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); //(7)!
|
||||
// // Reason Ignore: admonishment is in the wrong place visually with correct prettier rules
|
||||
// // prettier-ignore
|
||||
// test("user can add a block", async ({ page }) => { //(6)!
|
||||
// // workaround for #8788
|
||||
// await buildPage.navbar.clickBuildLink();
|
||||
// await test.expect(page).toHaveURL(new RegExp("/build"));
|
||||
// await buildPage.waitForPageLoad();
|
||||
// await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); //(7)!
|
||||
|
||||
await buildPage.closeTutorial(); //(9)!
|
||||
await buildPage.openBlocksPanel(); //(10)!
|
||||
const block = await buildPage.getDictionaryBlockDetails();
|
||||
// await buildPage.closeTutorial(); //(9)!
|
||||
// await buildPage.openBlocksPanel(); //(10)!
|
||||
// const block = await buildPage.getDictionaryBlockDetails();
|
||||
|
||||
await buildPage.addBlock(block); //(11)!
|
||||
await buildPage.closeBlocksPanel(); //(12)!
|
||||
await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy(); //(13)!
|
||||
});
|
||||
// --8<-- [end:BuildPageExample]
|
||||
// await buildPage.addBlock(block); //(11)!
|
||||
// await buildPage.closeBlocksPanel(); //(12)!
|
||||
// await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy(); //(13)!
|
||||
// });
|
||||
// // --8<-- [end:BuildPageExample]
|
||||
|
||||
test("user can add all blocks a-l", async ({ page }, testInfo) => {
|
||||
// this test is slow af so we 10x the timeout (sorry future me)
|
||||
await test.setTimeout(testInfo.timeout * 100);
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
await buildPage.closeTutorial();
|
||||
await buildPage.openBlocksPanel();
|
||||
const blocks = await buildPage.getBlocks();
|
||||
// test("user can add all blocks a-l", async ({ page }, testInfo) => {
|
||||
// // this test is slow af so we 10x the timeout (sorry future me)
|
||||
// await test.setTimeout(testInfo.timeout * 100);
|
||||
// await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
// await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
// await buildPage.closeTutorial();
|
||||
// await buildPage.openBlocksPanel();
|
||||
// const blocks = await buildPage.getBlocks();
|
||||
|
||||
const blockIdsToSkip = await buildPage.getBlocksToSkip();
|
||||
const blockTypesToSkip = ["Input", "Output", "Agent", "AI"];
|
||||
// const blockIdsToSkip = await buildPage.getBlocksToSkip();
|
||||
// const blockTypesToSkip = ["Input", "Output", "Agent", "AI"];
|
||||
|
||||
// add all the blocks in order except for the agent executor block
|
||||
for (const block of blocks) {
|
||||
if (block.name[0].toLowerCase() >= "m") {
|
||||
continue;
|
||||
}
|
||||
if (!blockIdsToSkip.some((b) => b === block.id) && !blockTypesToSkip.some((b) => block.type === b)) {
|
||||
console.log("Adding block:", block.name, block.id, block.type, " skipping types:", blockTypesToSkip);
|
||||
await buildPage.addBlock(block);
|
||||
}
|
||||
}
|
||||
await buildPage.closeBlocksPanel();
|
||||
// check that all the blocks are visible
|
||||
for (const block of blocks) {
|
||||
if (block.name[0].toLowerCase() >= "m") {
|
||||
continue;
|
||||
}
|
||||
if (!blockIdsToSkip.some((b) => b === block.id) && !blockTypesToSkip.some((b) => block.type === b)) {
|
||||
console.log("Checking block:", block.name, block.id, block.type, " skipping types:", blockTypesToSkip);
|
||||
await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy();
|
||||
}
|
||||
}
|
||||
// // add all the blocks in order except for the agent executor block
|
||||
// for (const block of blocks) {
|
||||
// if (block.name[0].toLowerCase() >= "m") {
|
||||
// continue;
|
||||
// }
|
||||
// if (!blockIdsToSkip.some((b) => b === block.id) && !blockTypesToSkip.some((b) => block.type === b)) {
|
||||
// console.log("Adding block:", block.name, block.id, block.type, " skipping types:", blockTypesToSkip);
|
||||
// await buildPage.addBlock(block);
|
||||
// }
|
||||
// }
|
||||
// await buildPage.closeBlocksPanel();
|
||||
// // check that all the blocks are visible
|
||||
// for (const block of blocks) {
|
||||
// if (block.name[0].toLowerCase() >= "m") {
|
||||
// continue;
|
||||
// }
|
||||
// if (!blockIdsToSkip.some((b) => b === block.id) && !blockTypesToSkip.some((b) => block.type === b)) {
|
||||
// console.log("Checking block:", block.name, block.id, block.type, " skipping types:", blockTypesToSkip);
|
||||
// await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy();
|
||||
// }
|
||||
// }
|
||||
|
||||
// check that we can save the agent with all the blocks
|
||||
await buildPage.saveAgent("all blocks test", "all blocks test");
|
||||
// page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
});
|
||||
// // check that we can save the agent with all the blocks
|
||||
// await buildPage.saveAgent("all blocks test", "all blocks test");
|
||||
// // page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340
|
||||
// await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
// });
|
||||
|
||||
test("user can add all blocks m-z", async ({ page }, testInfo) => {
|
||||
// this test is slow af so we 10x the timeout (sorry future me)
|
||||
await test.setTimeout(testInfo.timeout * 100);
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
await buildPage.closeTutorial();
|
||||
await buildPage.openBlocksPanel();
|
||||
const blocks = await buildPage.getBlocks();
|
||||
// test("user can add all blocks m-z", async ({ page }, testInfo) => {
|
||||
// // this test is slow af so we 10x the timeout (sorry future me)
|
||||
// await test.setTimeout(testInfo.timeout * 100);
|
||||
// await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
// await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
// await buildPage.closeTutorial();
|
||||
// await buildPage.openBlocksPanel();
|
||||
// const blocks = await buildPage.getBlocks();
|
||||
|
||||
const blockIdsToSkip = await buildPage.getBlocksToSkip();
|
||||
const blockTypesToSkip = ["Input", "Output", "Agent", "AI"];
|
||||
// const blockIdsToSkip = await buildPage.getBlocksToSkip();
|
||||
// const blockTypesToSkip = ["Input", "Output", "Agent", "AI"];
|
||||
|
||||
// add all the blocks in order except for the agent executor block
|
||||
for (const block of blocks) {
|
||||
if (block.name[0].toLowerCase() < "m") {
|
||||
continue;
|
||||
}
|
||||
if (!blockIdsToSkip.some((b) => b === block.id) && !blockTypesToSkip.some((b) => block.type === b)) {
|
||||
console.log("Adding block:", block.name, block.id, block.type, " skipping types:", blockTypesToSkip);
|
||||
await buildPage.addBlock(block);
|
||||
}
|
||||
}
|
||||
await buildPage.closeBlocksPanel();
|
||||
// check that all the blocks are visible
|
||||
for (const block of blocks) {
|
||||
if (block.name[0].toLowerCase() < "m") {
|
||||
continue;
|
||||
}
|
||||
if (!blockIdsToSkip.some((b) => b === block.id) && !blockTypesToSkip.some((b) => block.type === b)) {
|
||||
console.log("Checking block:", block.name, block.id, block.type, " skipping types:", blockTypesToSkip);
|
||||
await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy();
|
||||
}
|
||||
}
|
||||
// // add all the blocks in order except for the agent executor block
|
||||
// for (const block of blocks) {
|
||||
// if (block.name[0].toLowerCase() < "m") {
|
||||
// continue;
|
||||
// }
|
||||
// if (!blockIdsToSkip.some((b) => b === block.id) && !blockTypesToSkip.some((b) => block.type === b)) {
|
||||
// console.log("Adding block:", block.name, block.id, block.type, " skipping types:", blockTypesToSkip);
|
||||
// await buildPage.addBlock(block);
|
||||
// }
|
||||
// }
|
||||
// await buildPage.closeBlocksPanel();
|
||||
// // check that all the blocks are visible
|
||||
// for (const block of blocks) {
|
||||
// if (block.name[0].toLowerCase() < "m") {
|
||||
// continue;
|
||||
// }
|
||||
// if (!blockIdsToSkip.some((b) => b === block.id) && !blockTypesToSkip.some((b) => block.type === b)) {
|
||||
// console.log("Checking block:", block.name, block.id, block.type, " skipping types:", blockTypesToSkip);
|
||||
// await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy();
|
||||
// }
|
||||
// }
|
||||
|
||||
// check that we can save the agent with all the blocks
|
||||
await buildPage.saveAgent("all blocks test", "all blocks test");
|
||||
// page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
});
|
||||
// // check that we can save the agent with all the blocks
|
||||
// await buildPage.saveAgent("all blocks test", "all blocks test");
|
||||
// // page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340
|
||||
// await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
// });
|
||||
|
||||
test("build navigation is accessible from navbar", async ({ page }) => {
|
||||
await buildPage.navbar.clickBuildLink();
|
||||
await test.expect(page).toHaveURL(new RegExp("/build"));
|
||||
// workaround for #8788
|
||||
await page.reload();
|
||||
await page.reload();
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
});
|
||||
// test("build navigation is accessible from navbar", async ({ page }) => {
|
||||
// await buildPage.navbar.clickBuildLink();
|
||||
// await test.expect(page).toHaveURL(new RegExp("/build"));
|
||||
// // workaround for #8788
|
||||
// await page.reload();
|
||||
// await page.reload();
|
||||
// await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
// });
|
||||
|
||||
test("user can add two blocks and connect them", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await test.setTimeout(testInfo.timeout * 10);
|
||||
// test("user can add two blocks and connect them", async ({
|
||||
// page,
|
||||
// }, testInfo) => {
|
||||
// await test.setTimeout(testInfo.timeout * 10);
|
||||
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
await buildPage.closeTutorial();
|
||||
await buildPage.openBlocksPanel();
|
||||
// await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
// await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
// await buildPage.closeTutorial();
|
||||
// await buildPage.openBlocksPanel();
|
||||
|
||||
// Define the blocks to add
|
||||
const block1 = {
|
||||
id: "1ff065e9-88e8-4358-9d82-8dc91f622ba9",
|
||||
name: "Store Value 1",
|
||||
description: "Store Value Block 1",
|
||||
type: "Standard",
|
||||
};
|
||||
const block2 = {
|
||||
id: "1ff065e9-88e8-4358-9d82-8dc91f622ba9",
|
||||
name: "Store Value 2",
|
||||
description: "Store Value Block 2",
|
||||
type: "Standard",
|
||||
};
|
||||
// // Define the blocks to add
|
||||
// const block1 = {
|
||||
// id: "1ff065e9-88e8-4358-9d82-8dc91f622ba9",
|
||||
// name: "Store Value 1",
|
||||
// description: "Store Value Block 1",
|
||||
// type: "Standard",
|
||||
// };
|
||||
// const block2 = {
|
||||
// id: "1ff065e9-88e8-4358-9d82-8dc91f622ba9",
|
||||
// name: "Store Value 2",
|
||||
// description: "Store Value Block 2",
|
||||
// type: "Standard",
|
||||
// };
|
||||
|
||||
// Add the blocks
|
||||
await buildPage.addBlock(block1);
|
||||
await buildPage.addBlock(block2);
|
||||
await buildPage.closeBlocksPanel();
|
||||
// // Add the blocks
|
||||
// await buildPage.addBlock(block1);
|
||||
// await buildPage.addBlock(block2);
|
||||
// await buildPage.closeBlocksPanel();
|
||||
|
||||
// Connect the blocks
|
||||
await buildPage.connectBlockOutputToBlockInputViaDataId(
|
||||
"1-1-output-source",
|
||||
"1-2-input-target",
|
||||
);
|
||||
// // Connect the blocks
|
||||
// await buildPage.connectBlockOutputToBlockInputViaDataId(
|
||||
// "1-1-output-source",
|
||||
// "1-2-input-target",
|
||||
// );
|
||||
|
||||
// Fill in the input for the first block
|
||||
await buildPage.fillBlockInputByPlaceholder(
|
||||
block1.id,
|
||||
"Enter input",
|
||||
"Test Value",
|
||||
"1",
|
||||
);
|
||||
// // Fill in the input for the first block
|
||||
// await buildPage.fillBlockInputByPlaceholder(
|
||||
// block1.id,
|
||||
// "Enter input",
|
||||
// "Test Value",
|
||||
// "1",
|
||||
// );
|
||||
|
||||
// Save the agent and wait for the URL to update
|
||||
await buildPage.saveAgent(
|
||||
"Connected Blocks Test",
|
||||
"Testing block connections",
|
||||
);
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
// // Save the agent and wait for the URL to update
|
||||
// await buildPage.saveAgent(
|
||||
// "Connected Blocks Test",
|
||||
// "Testing block connections",
|
||||
// );
|
||||
// await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
|
||||
// Wait for the save button to be enabled again
|
||||
await buildPage.waitForSaveButton();
|
||||
// // Wait for the save button to be enabled again
|
||||
// await buildPage.waitForSaveButton();
|
||||
|
||||
// Ensure the run button is enabled
|
||||
await test.expect(buildPage.isRunButtonEnabled()).resolves.toBeTruthy();
|
||||
// // Ensure the run button is enabled
|
||||
// await test.expect(buildPage.isRunButtonEnabled()).resolves.toBeTruthy();
|
||||
|
||||
// Run the agent
|
||||
await buildPage.runAgent();
|
||||
// // Run the agent
|
||||
// await buildPage.runAgent();
|
||||
|
||||
// Wait for processing to complete by checking the completion badge
|
||||
await buildPage.waitForCompletionBadge();
|
||||
// // Wait for processing to complete by checking the completion badge
|
||||
// await buildPage.waitForCompletionBadge();
|
||||
|
||||
// Get the first completion badge and verify it's visible
|
||||
await test
|
||||
.expect(buildPage.isCompletionBadgeVisible())
|
||||
.resolves.toBeTruthy();
|
||||
});
|
||||
// // Get the first completion badge and verify it's visible
|
||||
// await test
|
||||
// .expect(buildPage.isCompletionBadgeVisible())
|
||||
// .resolves.toBeTruthy();
|
||||
// });
|
||||
|
||||
test("user can build an agent with inputs and output blocks", async ({
|
||||
page,
|
||||
}) => {
|
||||
// simple calculator to double input and output it
|
||||
// test("user can build an agent with inputs and output blocks", async ({
|
||||
// page,
|
||||
// }) => {
|
||||
// // simple calculator to double input and output it
|
||||
|
||||
// load the pages and prep
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
await buildPage.closeTutorial();
|
||||
await buildPage.openBlocksPanel();
|
||||
// // load the pages and prep
|
||||
// await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
// await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
// await buildPage.closeTutorial();
|
||||
// await buildPage.openBlocksPanel();
|
||||
|
||||
// find the blocks we want
|
||||
const blocks = await buildPage.getBlocks();
|
||||
const inputBlock = blocks.find((b) => b.name === "Agent Input");
|
||||
const outputBlock = blocks.find((b) => b.name === "Agent Output");
|
||||
const calculatorBlock = blocks.find((b) => b.name === "Calculator");
|
||||
if (!inputBlock || !outputBlock || !calculatorBlock) {
|
||||
throw new Error("Input or output block not found");
|
||||
}
|
||||
// // find the blocks we want
|
||||
// const blocks = await buildPage.getBlocks();
|
||||
// const inputBlock = blocks.find((b) => b.name === "Agent Input");
|
||||
// const outputBlock = blocks.find((b) => b.name === "Agent Output");
|
||||
// const calculatorBlock = blocks.find((b) => b.name === "Calculator");
|
||||
// if (!inputBlock || !outputBlock || !calculatorBlock) {
|
||||
// throw new Error("Input or output block not found");
|
||||
// }
|
||||
|
||||
// add the blocks
|
||||
await buildPage.addBlock(inputBlock);
|
||||
await buildPage.addBlock(outputBlock);
|
||||
await buildPage.addBlock(calculatorBlock);
|
||||
await buildPage.closeBlocksPanel();
|
||||
// // add the blocks
|
||||
// await buildPage.addBlock(inputBlock);
|
||||
// await buildPage.addBlock(outputBlock);
|
||||
// await buildPage.addBlock(calculatorBlock);
|
||||
// await buildPage.closeBlocksPanel();
|
||||
|
||||
// Wait for blocks to be fully loaded
|
||||
await page.waitForTimeout(1000);
|
||||
// // Wait for blocks to be fully loaded
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await test.expect(buildPage.hasBlock(inputBlock)).resolves.toBeTruthy();
|
||||
await test.expect(buildPage.hasBlock(outputBlock)).resolves.toBeTruthy();
|
||||
await test
|
||||
.expect(buildPage.hasBlock(calculatorBlock))
|
||||
.resolves.toBeTruthy();
|
||||
// await test.expect(buildPage.hasBlock(inputBlock)).resolves.toBeTruthy();
|
||||
// await test.expect(buildPage.hasBlock(outputBlock)).resolves.toBeTruthy();
|
||||
// await test
|
||||
// .expect(buildPage.hasBlock(calculatorBlock))
|
||||
// .resolves.toBeTruthy();
|
||||
|
||||
// Wait for blocks to be ready for connections
|
||||
await page.waitForTimeout(1000);
|
||||
// // Wait for blocks to be ready for connections
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
inputBlock.id,
|
||||
"Result",
|
||||
calculatorBlock.id,
|
||||
"A",
|
||||
);
|
||||
await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
inputBlock.id,
|
||||
"Result",
|
||||
calculatorBlock.id,
|
||||
"B",
|
||||
);
|
||||
await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
calculatorBlock.id,
|
||||
"Result",
|
||||
outputBlock.id,
|
||||
"Value",
|
||||
);
|
||||
// await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
// inputBlock.id,
|
||||
// "Result",
|
||||
// calculatorBlock.id,
|
||||
// "A",
|
||||
// );
|
||||
// await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
// inputBlock.id,
|
||||
// "Result",
|
||||
// calculatorBlock.id,
|
||||
// "B",
|
||||
// );
|
||||
// await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
// calculatorBlock.id,
|
||||
// "Result",
|
||||
// outputBlock.id,
|
||||
// "Value",
|
||||
// );
|
||||
|
||||
// Wait for connections to stabilize
|
||||
await page.waitForTimeout(1000);
|
||||
// // Wait for connections to stabilize
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await buildPage.fillBlockInputByPlaceholder(
|
||||
inputBlock.id,
|
||||
"Enter Name",
|
||||
"Value",
|
||||
);
|
||||
await buildPage.fillBlockInputByPlaceholder(
|
||||
outputBlock.id,
|
||||
"Enter Name",
|
||||
"Doubled",
|
||||
);
|
||||
// await buildPage.fillBlockInputByPlaceholder(
|
||||
// inputBlock.id,
|
||||
// "Enter Name",
|
||||
// "Value",
|
||||
// );
|
||||
// await buildPage.fillBlockInputByPlaceholder(
|
||||
// outputBlock.id,
|
||||
// "Enter Name",
|
||||
// "Doubled",
|
||||
// );
|
||||
|
||||
// Wait before changing dropdown
|
||||
await page.waitForTimeout(500);
|
||||
// // Wait before changing dropdown
|
||||
// await page.waitForTimeout(500);
|
||||
|
||||
await buildPage.selectBlockInputValue(
|
||||
calculatorBlock.id,
|
||||
"Operation",
|
||||
"Add",
|
||||
);
|
||||
// await buildPage.selectBlockInputValue(
|
||||
// calculatorBlock.id,
|
||||
// "Operation",
|
||||
// "Add",
|
||||
// );
|
||||
|
||||
// Wait before saving
|
||||
await page.waitForTimeout(1000);
|
||||
// // Wait before saving
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await buildPage.saveAgent(
|
||||
"Input and Output Blocks Test",
|
||||
"Testing input and output blocks",
|
||||
);
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
// await buildPage.saveAgent(
|
||||
// "Input and Output Blocks Test",
|
||||
// "Testing input and output blocks",
|
||||
// );
|
||||
// await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
|
||||
// Wait for save to complete
|
||||
await page.waitForTimeout(1000);
|
||||
// // Wait for save to complete
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await buildPage.runAgent();
|
||||
await buildPage.fillRunDialog({
|
||||
Value: "10",
|
||||
});
|
||||
await buildPage.clickRunDialogRunButton();
|
||||
await buildPage.waitForCompletionBadge();
|
||||
await test
|
||||
.expect(buildPage.isCompletionBadgeVisible())
|
||||
.resolves.toBeTruthy();
|
||||
});
|
||||
});
|
||||
// await buildPage.runAgent();
|
||||
// await buildPage.fillRunDialog({
|
||||
// Value: "10",
|
||||
// });
|
||||
// await buildPage.clickRunDialogRunButton();
|
||||
// await buildPage.waitForCompletionBadge();
|
||||
// await test
|
||||
// .expect(buildPage.isCompletionBadgeVisible())
|
||||
// .resolves.toBeTruthy();
|
||||
// });
|
||||
// });
|
||||
|
||||
@@ -1,126 +1,126 @@
|
||||
import { expect, TestInfo } from "@playwright/test";
|
||||
import { test } from "./fixtures";
|
||||
import { BuildPage } from "./pages/build.page";
|
||||
import { MonitorPage } from "./pages/monitor.page";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import * as fs from "fs/promises";
|
||||
import path from "path";
|
||||
// --8<-- [start:AttachAgentId]
|
||||
test.describe("Monitor", () => {
|
||||
let buildPage: BuildPage;
|
||||
let monitorPage: MonitorPage;
|
||||
// import { expect, TestInfo } from "@playwright/test";
|
||||
// import { test } from "./fixtures";
|
||||
// import { BuildPage } from "./pages/build.page";
|
||||
// import { MonitorPage } from "./pages/monitor.page";
|
||||
// import { v4 as uuidv4 } from "uuid";
|
||||
// import * as fs from "fs/promises";
|
||||
// import path from "path";
|
||||
// // --8<-- [start:AttachAgentId]
|
||||
// test.describe("Monitor", () => {
|
||||
// let buildPage: BuildPage;
|
||||
// let monitorPage: MonitorPage;
|
||||
|
||||
test.beforeEach(async ({ page, loginPage, testUser }, testInfo: TestInfo) => {
|
||||
buildPage = new BuildPage(page);
|
||||
monitorPage = new MonitorPage(page);
|
||||
// test.beforeEach(async ({ page, loginPage, testUser }, testInfo: TestInfo) => {
|
||||
// buildPage = new BuildPage(page);
|
||||
// monitorPage = new MonitorPage(page);
|
||||
|
||||
// Start each test with login using worker auth
|
||||
await page.goto("/login");
|
||||
await loginPage.login(testUser.email, testUser.password);
|
||||
await test.expect(page).toHaveURL("/marketplace");
|
||||
// // Start each test with login using worker auth
|
||||
// await page.goto("/login");
|
||||
// await loginPage.login(testUser.email, testUser.password);
|
||||
// await test.expect(page).toHaveURL("/marketplace");
|
||||
|
||||
// add a test agent
|
||||
const basicBlock = await buildPage.getDictionaryBlockDetails();
|
||||
const id = uuidv4();
|
||||
await buildPage.createSingleBlockAgent(
|
||||
`test-agent-${id}`,
|
||||
`test-agent-description-${id}`,
|
||||
basicBlock,
|
||||
);
|
||||
await buildPage.runAgent();
|
||||
// await monitorPage.navbar.clickMonitorLink();
|
||||
await page.goto("/monitoring"); // Library link now points to /library
|
||||
await monitorPage.waitForPageLoad();
|
||||
await test.expect(monitorPage.isLoaded()).resolves.toBeTruthy();
|
||||
testInfo.attach("agent-id", { body: id });
|
||||
});
|
||||
// --8<-- [end:AttachAgentId]
|
||||
// // add a test agent
|
||||
// const basicBlock = await buildPage.getDictionaryBlockDetails();
|
||||
// const id = uuidv4();
|
||||
// await buildPage.createSingleBlockAgent(
|
||||
// `test-agent-${id}`,
|
||||
// `test-agent-description-${id}`,
|
||||
// basicBlock,
|
||||
// );
|
||||
// await buildPage.runAgent();
|
||||
// // await monitorPage.navbar.clickMonitorLink();
|
||||
// await page.goto("/monitoring"); // Library link now points to /library
|
||||
// await monitorPage.waitForPageLoad();
|
||||
// await test.expect(monitorPage.isLoaded()).resolves.toBeTruthy();
|
||||
// testInfo.attach("agent-id", { body: id });
|
||||
// });
|
||||
// // --8<-- [end:AttachAgentId]
|
||||
|
||||
test.afterAll(async ({}) => {
|
||||
// clear out the downloads folder
|
||||
console.log(
|
||||
`clearing out the downloads folder ${monitorPage.downloadsFolder}`,
|
||||
);
|
||||
// test.afterAll(async ({}) => {
|
||||
// // clear out the downloads folder
|
||||
// console.log(
|
||||
// `clearing out the downloads folder ${monitorPage.downloadsFolder}`,
|
||||
// );
|
||||
|
||||
await fs.rm(`${monitorPage.downloadsFolder}/monitor`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
});
|
||||
// await fs.rm(`${monitorPage.downloadsFolder}/monitor`, {
|
||||
// recursive: true,
|
||||
// force: true,
|
||||
// });
|
||||
// });
|
||||
|
||||
test("user can view agents", async ({ page }) => {
|
||||
const agents = await monitorPage.listAgents();
|
||||
// there should be at least one agent
|
||||
await test.expect(agents.length).toBeGreaterThan(0);
|
||||
});
|
||||
// test("user can view agents", async ({ page }) => {
|
||||
// const agents = await monitorPage.listAgents();
|
||||
// // there should be at least one agent
|
||||
// await test.expect(agents.length).toBeGreaterThan(0);
|
||||
// });
|
||||
|
||||
test.skip("user can export and import agents", async ({
|
||||
page,
|
||||
}, testInfo: TestInfo) => {
|
||||
// --8<-- [start:ReadAgentId]
|
||||
if (testInfo.attachments.length === 0 || !testInfo.attachments[0].body) {
|
||||
throw new Error("No agent id attached to the test");
|
||||
}
|
||||
const testAttachName = testInfo.attachments[0].body.toString();
|
||||
// --8<-- [end:ReadAgentId]
|
||||
const agents = await monitorPage.listAgents();
|
||||
// test.skip("user can export and import agents", async ({
|
||||
// page,
|
||||
// }, testInfo: TestInfo) => {
|
||||
// // --8<-- [start:ReadAgentId]
|
||||
// if (testInfo.attachments.length === 0 || !testInfo.attachments[0].body) {
|
||||
// throw new Error("No agent id attached to the test");
|
||||
// }
|
||||
// const testAttachName = testInfo.attachments[0].body.toString();
|
||||
// // --8<-- [end:ReadAgentId]
|
||||
// const agents = await monitorPage.listAgents();
|
||||
|
||||
const downloadPromise = page.waitForEvent("download");
|
||||
const agent = agents.find(
|
||||
(a: any) => a.name === `test-agent-${testAttachName}`,
|
||||
);
|
||||
if (!agent) {
|
||||
throw new Error(`Agent ${testAttachName} not found`);
|
||||
}
|
||||
await monitorPage.exportToFile(agent);
|
||||
const download = await downloadPromise;
|
||||
// const downloadPromise = page.waitForEvent("download");
|
||||
// const agent = agents.find(
|
||||
// (a: any) => a.name === `test-agent-${testAttachName}`,
|
||||
// );
|
||||
// if (!agent) {
|
||||
// throw new Error(`Agent ${testAttachName} not found`);
|
||||
// }
|
||||
// await monitorPage.exportToFile(agent);
|
||||
// const download = await downloadPromise;
|
||||
|
||||
// Wait for the download process to complete and save the downloaded file somewhere.
|
||||
await download.saveAs(
|
||||
`${monitorPage.downloadsFolder}/monitor/${download.suggestedFilename()}`,
|
||||
);
|
||||
console.log(`downloaded file to ${download.suggestedFilename()}`);
|
||||
await test.expect(download.suggestedFilename()).toBeDefined();
|
||||
// test-agent-uuid-v1.json
|
||||
await test.expect(download.suggestedFilename()).toContain("test-agent-");
|
||||
await test.expect(download.suggestedFilename()).toContain("v1.json");
|
||||
// // Wait for the download process to complete and save the downloaded file somewhere.
|
||||
// await download.saveAs(
|
||||
// `${monitorPage.downloadsFolder}/monitor/${download.suggestedFilename()}`,
|
||||
// );
|
||||
// console.log(`downloaded file to ${download.suggestedFilename()}`);
|
||||
// await test.expect(download.suggestedFilename()).toBeDefined();
|
||||
// // test-agent-uuid-v1.json
|
||||
// await test.expect(download.suggestedFilename()).toContain("test-agent-");
|
||||
// await test.expect(download.suggestedFilename()).toContain("v1.json");
|
||||
|
||||
// import the agent
|
||||
const preImportAgents = await monitorPage.listAgents();
|
||||
const filesInFolder = await fs.readdir(
|
||||
`${monitorPage.downloadsFolder}/monitor`,
|
||||
);
|
||||
const importFile = filesInFolder.find((f) => f.includes(testAttachName));
|
||||
if (!importFile) {
|
||||
throw new Error(`No import file found for agent ${testAttachName}`);
|
||||
}
|
||||
const baseName = importFile.split(".")[0];
|
||||
await monitorPage.importFromFile(
|
||||
path.resolve(monitorPage.downloadsFolder, "monitor"),
|
||||
importFile,
|
||||
baseName + "-imported",
|
||||
);
|
||||
// // import the agent
|
||||
// const preImportAgents = await monitorPage.listAgents();
|
||||
// const filesInFolder = await fs.readdir(
|
||||
// `${monitorPage.downloadsFolder}/monitor`,
|
||||
// );
|
||||
// const importFile = filesInFolder.find((f) => f.includes(testAttachName));
|
||||
// if (!importFile) {
|
||||
// throw new Error(`No import file found for agent ${testAttachName}`);
|
||||
// }
|
||||
// const baseName = importFile.split(".")[0];
|
||||
// await monitorPage.importFromFile(
|
||||
// path.resolve(monitorPage.downloadsFolder, "monitor"),
|
||||
// importFile,
|
||||
// baseName + "-imported",
|
||||
// );
|
||||
|
||||
// You'll be dropped at the build page, so hit run and then go back to monitor
|
||||
await buildPage.runAgent();
|
||||
await monitorPage.navbar.clickMonitorLink();
|
||||
await monitorPage.waitForPageLoad();
|
||||
// // You'll be dropped at the build page, so hit run and then go back to monitor
|
||||
// await buildPage.runAgent();
|
||||
// await monitorPage.navbar.clickMonitorLink();
|
||||
// await monitorPage.waitForPageLoad();
|
||||
|
||||
const postImportAgents = await monitorPage.listAgents();
|
||||
await test
|
||||
.expect(postImportAgents.length)
|
||||
.toBeGreaterThan(preImportAgents.length);
|
||||
console.log(`postImportAgents: ${JSON.stringify(postImportAgents)}`);
|
||||
const importedAgent = postImportAgents.find(
|
||||
(a: any) => a.name === `${baseName}-imported`,
|
||||
);
|
||||
await test.expect(importedAgent).toBeDefined();
|
||||
});
|
||||
// const postImportAgents = await monitorPage.listAgents();
|
||||
// await test
|
||||
// .expect(postImportAgents.length)
|
||||
// .toBeGreaterThan(preImportAgents.length);
|
||||
// console.log(`postImportAgents: ${JSON.stringify(postImportAgents)}`);
|
||||
// const importedAgent = postImportAgents.find(
|
||||
// (a: any) => a.name === `${baseName}-imported`,
|
||||
// );
|
||||
// await test.expect(importedAgent).toBeDefined();
|
||||
// });
|
||||
|
||||
test("user can view runs", async ({ page }) => {
|
||||
const runs = await monitorPage.listRuns();
|
||||
console.log(runs);
|
||||
// there should be at least one run
|
||||
await test.expect(runs.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
// test("user can view runs", async ({ page }) => {
|
||||
// const runs = await monitorPage.listRuns();
|
||||
// console.log(runs);
|
||||
// // there should be at least one run
|
||||
// await test.expect(runs.length).toBeGreaterThan(0);
|
||||
// });
|
||||
// });
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
import scrollbarHide from "tailwind-scrollbar-hide";
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
@@ -141,7 +142,11 @@ const config = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
plugins: [
|
||||
require("tailwindcss-animate"),
|
||||
scrollbarHide,
|
||||
require("tailwind-scrollbar"),
|
||||
],
|
||||
} satisfies Config;
|
||||
|
||||
export default config;
|
||||
|
||||