fix(frontend): add typechecks and fix existing type errors in frontend (#9336)

<!-- Clearly explain the need for these changes: -->
We want to be able to use typechecking and see errors before they occur.
This is a PR to help enable us to do so by fixing the existing errors
and hopefully not causing new ones.

### Changes 🏗️
- adds check to ci
- disables some code points
- fixes lots of type errors
- fixes a bunch of the stories

<!-- Concisely describe all of the changes made in this pull request:
-->

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [x] added types
  - [x] Ran some of the stories
  - [x] Asked all the relevant parties for manual checks

---------

Co-authored-by: SwiftyOS <craigswift13@gmail.com>
This commit is contained in:
Nicholas Tindle
2025-01-29 12:32:35 +00:00
committed by GitHub
parent 5e2043b774
commit 97a26dba7e
36 changed files with 825 additions and 304 deletions

View File

@@ -37,6 +37,25 @@ jobs:
run: |
yarn lint
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "21"
- name: Install dependencies
run: |
yarn install --frozen-lockfile
- name: Run tsc check
run: |
yarn type-check
test:
runs-on: ubuntu-latest
strategy:

View File

@@ -1,18 +1,18 @@
import { withRoleAccess } from "@/lib/withRoleAccess";
import React from "react";
import { getReviewableAgents } from "@/components/admin/marketplace/actions";
import AdminMarketplaceAgentList from "@/components/admin/marketplace/AdminMarketplaceAgentList";
import AdminFeaturedAgentsControl from "@/components/admin/marketplace/AdminFeaturedAgentsControl";
// import { getReviewableAgents } from "@/components/admin/marketplace/actions";
// import AdminMarketplaceAgentList from "@/components/admin/marketplace/AdminMarketplaceAgentList";
// import AdminFeaturedAgentsControl from "@/components/admin/marketplace/AdminFeaturedAgentsControl";
import { Separator } from "@/components/ui/separator";
async function AdminMarketplace() {
const reviewableAgents = await getReviewableAgents();
// const reviewableAgents = await getReviewableAgents();
return (
<>
<AdminMarketplaceAgentList agents={reviewableAgents.items} />
<Separator className="my-4" />
<AdminFeaturedAgentsControl className="mt-4" />
{/* <AdminMarketplaceAgentList agents={reviewableAgents.items} />
<Separator className="my-4" />
<AdminFeaturedAgentsControl className="mt-4" /> */}
</>
);
}

View File

@@ -179,6 +179,7 @@ export default function PrivatePage() {
{
oauth2: "OAuth2 credentials",
api_key: "API key",
user_password: "Username & password",
}[cred.type]
}{" "}
- <code>{cred.id}</code>

View File

@@ -151,11 +151,7 @@ function SearchResults({
<div className="min-h-[500px] max-w-[1440px]">
{showAgents && agentsCount > 0 && (
<div className="mt-[36px]">
<AgentsSection
agents={agents}
sectionTitle="Agents"
className="font-[Large-Poppins] text-[18px] font-semibold leading-[28px]"
/>
<AgentsSection agents={agents} sectionTitle="Agents" />
</div>
)}

View File

@@ -250,7 +250,7 @@ const FlowEditor: React.FC<{
if (deletedNodeData) {
history.push({
type: "DELETE_NODE",
payload: { node: deletedNodeData },
payload: { node: deletedNodeData.data },
undo: () => addNodes(deletedNodeData),
redo: () => deleteElements({ nodes: [{ id: nodeID }] }),
});

View File

@@ -9,6 +9,7 @@ import RunnerOutputUI from "./runner-ui/RunnerOutputUI";
import { Node } from "@xyflow/react";
import { filterBlocksByType } from "@/lib/utils";
import { BlockIORootSchema, BlockUIType } from "@/lib/autogpt-server-api/types";
import { CustomNode } from "./CustomNode";
interface HardcodedValues {
name: any;
@@ -27,7 +28,7 @@ export interface InputItem {
interface RunnerUIWrapperProps {
nodes: Node[];
setNodes: React.Dispatch<React.SetStateAction<Node[]>>;
setNodes: React.Dispatch<React.SetStateAction<CustomNode[]>>;
setIsScheduling: React.Dispatch<React.SetStateAction<boolean>>;
isRunning: boolean;
isScheduling: boolean;

View File

@@ -10,7 +10,6 @@ const meta = {
},
tags: ["autodocs"],
argTypes: {
onRunAgent: { action: "run agent clicked" },
name: { control: "text" },
creator: { control: "text" },
shortDescription: { control: "text" },
@@ -28,8 +27,8 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
onRunAgent: () => console.log("Run agent clicked"),
name: "AI Video Generator",
storeListingVersionId: "123",
creator: "Toran Richards",
shortDescription:
"Transform ideas into breathtaking images with this AI-powered Image Generator.",

View File

@@ -15,40 +15,64 @@ type Story = StoryObj<typeof AgentTable>;
const sampleAgents: AgentTableRowProps[] = [
{
id: "agent-1",
id: 43,
agentName: "Super Coder",
description: "An AI agent that writes clean, efficient code",
imageSrc:
imageSrc: [
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
],
dateSubmitted: "2023-05-15",
status: "approved",
runs: 1500,
rating: 4.8,
onEdit: () => console.log("Edit Super Coder"),
agent_id: "43",
agent_version: 1,
sub_heading: "Super Coder",
date_submitted: "2023-05-15",
onEditSubmission: () => console.log("Edit Super Coder"),
onDeleteSubmission: () => console.log("Delete Super Coder"),
selectedAgents: new Set(),
setSelectedAgents: () => {},
},
{
id: "agent-2",
id: 44,
agentName: "Data Analyzer",
description: "Processes and analyzes large datasets with ease",
imageSrc:
imageSrc: [
"https://ddz4ak4pa3d19.cloudfront.net/cache/40/f7/40f7bc97c952f8df0f9c88d29defe8d4.jpg",
],
dateSubmitted: "2023-05-10",
status: "awaiting_review",
runs: 1200,
rating: 4.5,
onEdit: () => console.log("Edit Data Analyzer"),
agent_id: "44",
agent_version: 1,
sub_heading: "Data Analyzer",
date_submitted: "2023-05-10",
onEditSubmission: () => console.log("Edit Data Analyzer"),
onDeleteSubmission: () => console.log("Delete Data Analyzer"),
selectedAgents: new Set(),
setSelectedAgents: () => {},
},
{
id: "agent-3",
id: 45,
agentName: "UI Designer",
description: "Creates beautiful and intuitive user interfaces",
imageSrc:
imageSrc: [
"https://ddz4ak4pa3d19.cloudfront.net/cache/14/9e/149ebb9014aa8c0097e72ed89845af0e.jpg",
],
dateSubmitted: "2023-05-05",
status: "draft",
runs: 800,
rating: 4.2,
onEdit: () => console.log("Edit UI Designer"),
agent_id: "45",
agent_version: 1,
sub_heading: "UI Designer",
date_submitted: "2023-05-05",
onEditSubmission: () => console.log("Edit UI Designer"),
onDeleteSubmission: () => console.log("Delete UI Designer"),
selectedAgents: new Set(),
setSelectedAgents: () => {},
},
];

View File

@@ -16,13 +16,13 @@ export const Default: Story = {
args: {
agentName: "Super Coder",
description: "An AI agent that writes clean, efficient code",
imageSrc:
imageSrc: [
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
],
dateSubmitted: "2023-05-15",
status: "ACTIVE" as StatusType,
runs: 1500,
rating: 4.8,
onEdit: () => console.log("Edit Super Coder"),
},
};

View File

@@ -4,6 +4,7 @@ import * as React from "react";
import Image from "next/image";
import { IconStarFilled, IconMore } from "@/components/ui/icons";
import { Status, StatusType } from "./Status";
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api";
export interface AgentTableCardProps {
agent_id: string;

View File

@@ -11,7 +11,6 @@ const meta = {
tags: ["autodocs"],
argTypes: {
title: { control: "text" },
heading: { control: "text" },
description: { control: "text" },
buttonText: { control: "text" },
onButtonClick: { action: "buttonClicked" },
@@ -24,7 +23,6 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
title: "Want to contribute?",
heading: "We're always looking for more Creators!",
description: "Join our ever-growing community of hackers and tinkerers",
buttonText: "Become a Creator",
onButtonClick: () => console.log("Button clicked"),
@@ -34,7 +32,6 @@ export const Default: Story = {
export const CustomText: Story = {
args: {
title: "Become a Creator Today!",
heading: "Join Our Creator Community",
description: "Share your ideas and build amazing AI agents with us",
buttonText: "Start Creating",
onButtonClick: () => console.log("Custom button clicked"),

View File

@@ -15,7 +15,6 @@ const meta = {
bio: { control: "text" },
agentsUploaded: { control: "number" },
onClick: { action: "clicked" },
avatarSrc: { control: "text" },
},
} satisfies Meta<typeof CreatorCard>;
@@ -24,49 +23,49 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
index: 0,
creatorName: "John Doe",
creatorImage:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
bio: "AI enthusiast and developer with a passion for creating innovative agents.",
agentsUploaded: 15,
onClick: () => console.log("Default CreatorCard clicked"),
avatarSrc: "https://github.com/shadcn.png",
},
};
export const NewCreator: Story = {
args: {
index: 1,
creatorName: "Jane Smith",
creatorImage:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
bio: "Excited to start my journey in AI agent development!",
agentsUploaded: 1,
onClick: () => console.log("NewCreator CreatorCard clicked"),
avatarSrc: "https://example.com/avatar2.jpg",
},
};
export const ExperiencedCreator: Story = {
args: {
index: 2,
creatorName: "Alex Johnson",
creatorImage:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
bio: "Veteran AI researcher with a focus on natural language processing and machine learning.",
agentsUploaded: 50,
onClick: () => console.log("ExperiencedCreator CreatorCard clicked"),
avatarSrc: "https://example.com/avatar3.jpg",
},
};
export const WithInteraction: Story = {
args: {
index: 3,
creatorName: "Sam Brown",
creatorImage:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
bio: "Exploring the frontiers of AI and its applications in everyday life.",
agentsUploaded: 30,
onClick: () => console.log("WithInteraction CreatorCard clicked"),
avatarSrc: "https://example.com/avatar4.jpg",
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@@ -21,51 +21,48 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
links: {
website: "https://example.com",
linkedin: "https://linkedin.com/in/johndoe",
github: "https://github.com/johndoe",
other: ["https://twitter.com/johndoe", "https://medium.com/@johndoe"],
},
links: [
"https://example.com",
"https://linkedin.com/in/johndoe",
"https://github.com/johndoe",
"https://twitter.com/johndoe",
"https://medium.com/@johndoe",
],
},
};
export const WebsiteOnly: Story = {
args: {
links: {
website: "https://example.com",
},
links: ["https://example.com"],
},
};
export const SocialLinks: Story = {
args: {
links: {
linkedin: "https://linkedin.com/in/janedoe",
github: "https://github.com/janedoe",
other: ["https://twitter.com/janedoe"],
},
links: [
"https://linkedin.com/in/janedoe",
"https://github.com/janedoe",
"https://twitter.com/janedoe",
],
},
};
export const NoLinks: Story = {
args: {
links: {},
links: [],
},
};
export const MultipleOtherLinks: Story = {
args: {
links: {
website: "https://example.com",
linkedin: "https://linkedin.com/in/creator",
github: "https://github.com/creator",
other: [
"https://twitter.com/creator",
"https://medium.com/@creator",
"https://youtube.com/@creator",
"https://tiktok.com/@creator",
],
},
links: [
"https://example.com",
"https://linkedin.com/in/creator",
"https://github.com/creator",
"https://twitter.com/creator",
"https://medium.com/@creator",
"https://youtube.com/@creator",
"https://tiktok.com/@creator",
],
},
};

View File

@@ -43,6 +43,7 @@ export const Default: Story = {
runs: 50000,
rating: 4.7,
onClick: () => console.log("Card clicked"),
backgroundColor: "bg-white",
},
};
@@ -60,6 +61,7 @@ export const LowRating: Story = {
runs: 10000,
rating: 2.8,
onClick: () => console.log("Card clicked"),
backgroundColor: "bg-white",
},
};
@@ -77,6 +79,7 @@ export const HighRuns: Story = {
runs: 1000000,
rating: 4.9,
onClick: () => console.log("Card clicked"),
backgroundColor: "bg-white",
},
};
@@ -92,6 +95,7 @@ export const NoCreatorImage: Story = {
runs: 75000,
rating: 4.5,
onClick: () => console.log("Card clicked"),
backgroundColor: "bg-white",
},
};
@@ -108,6 +112,7 @@ export const ShortDescription: Story = {
runs: 50000,
rating: 4.2,
onClick: () => console.log("Card clicked"),
backgroundColor: "bg-white",
},
};
@@ -125,6 +130,7 @@ export const WithInteraction: Story = {
runs: 200000,
rating: 4.6,
onClick: () => console.log("Card clicked"),
backgroundColor: "bg-white",
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@@ -13,7 +13,6 @@ const meta = {
argTypes: {
userName: { control: "text" },
userEmail: { control: "text" },
activeLink: { control: "text" },
avatarSrc: { control: "text" },
menuItemGroups: { control: "object" },
},
@@ -67,7 +66,6 @@ export const Default: Story = {
args: {
userName: "John Doe",
userEmail: "john.doe@example.com",
activeLink: "/marketplace",
avatarSrc: "https://avatars.githubusercontent.com/u/123456789?v=4",
menuItemGroups: defaultMenuItemGroups,
},
@@ -77,7 +75,6 @@ export const NoAvatar: Story = {
args: {
userName: "Jane Smith",
userEmail: "jane.smith@example.com",
activeLink: "/library",
menuItemGroups: defaultMenuItemGroups,
},
};
@@ -86,7 +83,6 @@ export const LongUserName: Story = {
args: {
userName: "Alexander Bartholomew Christopherson III",
userEmail: "alexander@example.com",
activeLink: "/builder",
avatarSrc: "https://avatars.githubusercontent.com/u/987654321?v=4",
menuItemGroups: defaultMenuItemGroups,
},

View File

@@ -1,9 +1,10 @@
import type { Meta, StoryObj } from "@storybook/react";
import Navbar from "./Navbar";
import { Navbar } from "./Navbar";
import { userEvent, within } from "@storybook/test";
import { IconType } from "../ui/icons";
import { ProfileDetails } from "@/lib/autogpt-server-api/types";
import { jest } from "@jest/globals";
// You can't import this here, jest is not available in storybook and will crash it
// import { jest } from "@jest/globals";
// Mock the API responses
const mockProfileData: ProfileDetails = {
@@ -19,14 +20,14 @@ const mockCreditData = {
};
// Mock the API module
jest.mock("@/lib/autogpt-server-api", () => {
return function () {
return {
getStoreProfile: () => Promise.resolve(mockProfileData),
getUserCredit: () => Promise.resolve(mockCreditData),
};
};
});
// jest.mock("@/lib/autogpt-server-api", () => {
// return function () {
// return {
// getStoreProfile: () => Promise.resolve(mockProfileData),
// getUserCredit: () => Promise.resolve(mockCreditData),
// };
// };
// });
const meta = {
title: "AGPT UI/Navbar",
@@ -36,12 +37,12 @@ const meta = {
},
tags: ["autodocs"],
argTypes: {
isLoggedIn: { control: "boolean" },
avatarSrc: { control: "text" },
// isLoggedIn: { control: "boolean" },
// avatarSrc: { control: "text" },
links: { control: "object" },
activeLink: { control: "text" },
// activeLink: { control: "text" },
menuItemGroups: { control: "object" },
params: { control: { type: "object", defaultValue: { lang: "en" } } },
// params: { control: { type: "object", defaultValue: { lang: "en" } } },
},
} satisfies Meta<typeof Navbar>;
@@ -90,11 +91,11 @@ const defaultLinks = [
export const Default: Story = {
args: {
params: { lang: "en" },
isLoggedIn: true,
// params: { lang: "en" },
// isLoggedIn: true,
links: defaultLinks,
activeLink: "/marketplace",
avatarSrc: mockProfileData.avatar_url,
// activeLink: "/marketplace",
// avatarSrc: mockProfileData.avatar_url,
menuItemGroups: defaultMenuItemGroups,
},
};
@@ -102,21 +103,21 @@ export const Default: Story = {
export const WithActiveLink: Story = {
args: {
...Default.args,
activeLink: "/library",
// activeLink: "/library",
},
};
export const LongUserName: Story = {
args: {
...Default.args,
avatarSrc: "https://avatars.githubusercontent.com/u/987654321?v=4",
// avatarSrc: "https://avatars.githubusercontent.com/u/987654321?v=4",
},
};
export const NoAvatar: Story = {
args: {
...Default.args,
avatarSrc: undefined,
// avatarSrc: undefined,
},
};
@@ -138,8 +139,8 @@ export const WithInteraction: Story = {
export const NotLoggedIn: Story = {
args: {
...Default.args,
isLoggedIn: false,
avatarSrc: undefined,
// isLoggedIn: false,
// avatarSrc: undefined,
},
};

View File

@@ -63,12 +63,7 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
<IconAutoGPTLogo className="h-full w-full" />
</div>
{links.map((link) => (
<NavbarLink
key={link.name}
name={link.name}
href={link.href}
className="font-poppins text-[20px] leading-[28px]"
/>
<NavbarLink key={link.name} name={link.name} href={link.href} />
))}
</div>
{/* Profile section */}

View File

@@ -20,7 +20,11 @@ export const NavbarLink = ({ name, href }: NavbarLinkProps) => {
const activeLink = "/" + (parts.length > 2 ? parts[2] : parts[1]);
return (
<Link href={href} data-testid={`navbar-link-${name.toLowerCase()}`}>
<Link
href={href}
data-testid={`navbar-link-${name.toLowerCase()}`}
className="font-poppins text-[20px] leading-[28px]"
>
<div
className={`h-[48px] px-5 py-4 ${
activeLink === href

View File

@@ -9,29 +9,33 @@ const meta: Meta<typeof ProfileInfoForm> = {
},
tags: ["autodocs"],
argTypes: {
displayName: {
control: "text",
description: "The display name of the user",
},
handle: {
control: "text",
description: "The user's handle/username",
},
bio: {
control: "text",
description: "User's biography text",
},
profileImage: {
control: "text",
description: "URL of the user's profile image",
},
links: {
profile: {
control: "object",
description: "Array of social media links",
},
categories: {
control: "object",
description: "Array of selected categories",
description: "The profile details of the user",
displayName: {
control: "text",
description: "The display name of the user",
},
handle: {
control: "text",
description: "The user's handle/username",
},
bio: {
control: "text",
description: "User's biography text",
},
profileImage: {
control: "text",
description: "URL of the user's profile image",
},
links: {
control: "object",
description: "Array of social media links",
},
categories: {
control: "object",
description: "Array of selected categories",
},
},
},
};
@@ -41,30 +45,35 @@ type Story = StoryObj<typeof ProfileInfoForm>;
export const Empty: Story = {
args: {
displayName: "",
handle: "",
bio: "",
profileImage: undefined,
links: [],
categories: [],
profile: {
name: "",
username: "",
description: "",
avatar_url: "",
links: [],
top_categories: [],
agent_rating: 0,
agent_runs: 0,
},
},
};
export const Filled: Story = {
args: {
displayName: "Olivia Grace",
handle: "@ograce1421",
bio: "Our agents are designed to bring happiness and positive vibes to your daily routine. Each template helps you create and live more efficiently.",
profileImage: "https://via.placeholder.com/130x130",
links: [
{ id: 1, url: "www.websitelink.com" },
{ id: 2, url: "twitter.com/oliviagrace" },
{ id: 3, url: "github.com/ograce" },
],
categories: [
{ id: 1, name: "Entertainment" },
{ id: 2, name: "Blog" },
{ id: 3, name: "Content creation" },
],
profile: {
name: "Olivia Grace",
username: "@ograce1421",
description:
"Our agents are designed to bring happiness and positive vibes to your daily routine. Each template helps you create and live more efficiently.",
avatar_url: "https://via.placeholder.com/130x130",
links: [
"www.websitelink.com",
"twitter.com/oliviagrace",
"github.com/ograce",
],
top_categories: ["Entertainment", "Blog", "Content creation"],
agent_rating: 4.5,
agent_runs: 100,
},
},
};

View File

@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { PublishAgentSelect } from "./PublishAgentSelect";
import { Agent, PublishAgentSelect } from "./PublishAgentSelect";
const meta: Meta<typeof PublishAgentSelect> = {
title: "AGPT UI/Publish Agent Select",
@@ -10,51 +10,69 @@ const meta: Meta<typeof PublishAgentSelect> = {
export default meta;
type Story = StoryObj<typeof PublishAgentSelect>;
const mockAgents = [
const mockAgents: Agent[] = [
{
name: "SEO Optimizer",
lastEdited: "2 days ago",
imageSrc: "https://picsum.photos/seed/seo/300/200",
id: "1",
version: 1,
},
{
name: "Content Writer",
lastEdited: "5 days ago",
imageSrc: "https://picsum.photos/seed/writer/300/200",
id: "1",
version: 1,
},
{
name: "Data Analyzer",
lastEdited: "1 week ago",
imageSrc: "https://picsum.photos/seed/data/300/200",
id: "1",
version: 1,
},
{
name: "Image Recognition",
lastEdited: "2 weeks ago",
imageSrc: "https://picsum.photos/seed/image/300/200",
id: "1",
version: 1,
},
{
name: "Chatbot Assistant",
lastEdited: "3 weeks ago",
imageSrc: "https://picsum.photos/seed/chat/300/200",
id: "1",
version: 1,
},
{
name: "Code Generator",
lastEdited: "1 month ago",
imageSrc: "https://picsum.photos/seed/code/300/200",
id: "1",
version: 1,
},
{
name: "AI Translator",
lastEdited: "6 weeks ago",
imageSrc: "https://picsum.photos/seed/translate/300/200",
id: "1",
version: 1,
},
{
name: "Voice Assistant",
lastEdited: "2 months ago",
imageSrc: "https://picsum.photos/seed/voice/300/200",
id: "1",
version: 1,
},
{
name: "Data Visualizer",
lastEdited: "3 months ago",
imageSrc: "https://picsum.photos/seed/visualize/300/200",
id: "1",
version: 1,
},
];

View File

@@ -29,6 +29,8 @@ export const Filled: Story = {
args: {
...Default.args,
initialData: {
agent_id: "1",
slug: "super-seo-optimizer",
title: "Super SEO Optimizer",
subheader: "Boost your website's search engine rankings",
thumbnailSrc: "https://picsum.photos/seed/seo/500/350",
@@ -44,6 +46,8 @@ export const ThreeImages: Story = {
args: {
...Default.args,
initialData: {
agent_id: "1",
slug: "super-seo-optimizer",
title: "Multi-Image Agent",
subheader: "Showcasing multiple images",
thumbnailSrc: "https://picsum.photos/seed/initial/500/350",
@@ -63,6 +67,8 @@ export const SixImages: Story = {
args: {
...Default.args,
initialData: {
agent_id: "1",
slug: "super-seo-optimizer",
title: "Gallery Agent",
subheader: "Showcasing a gallery of images",
thumbnailSrc: "https://picsum.photos/seed/gallery1/500/350",

View File

@@ -6,6 +6,18 @@ import { Button } from "../agptui/Button";
import { IconClose, IconPlus } from "../ui/icons";
import BackendAPI from "@/lib/autogpt-server-api";
export interface PublishAgentInfoInitialData {
agent_id: string;
title: string;
subheader: string;
slug: string;
thumbnailSrc: string;
youtubeLink: string;
category: string;
description: string;
additionalImages?: string[];
}
interface PublishAgentInfoProps {
onBack: () => void;
onSubmit: (
@@ -18,17 +30,7 @@ interface PublishAgentInfoProps {
categories: string[],
) => void;
onClose: () => void;
initialData?: {
agent_id: string;
title: string;
subheader: string;
slug: string;
thumbnailSrc: string;
youtubeLink: string;
category: string;
description: string;
additionalImages?: string[];
};
initialData?: PublishAgentInfoInitialData;
}
export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({

View File

@@ -16,29 +16,32 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
agentName: "Test Agent",
onSubmit: (rating) => {
console.log("Rating submitted:", rating);
},
onClose: () => {
console.log("Rating card closed");
},
// onSubmit: (rating) => {
// console.log("Rating submitted:", rating);
// },
// onClose: () => {
// console.log("Rating card closed");
// },
storeListingVersionId: "1",
},
};
export const LongAgentName: Story = {
args: {
agentName: "Very Long Agent Name That Might Need Special Handling",
onSubmit: (rating) => {
console.log("Rating submitted:", rating);
},
onClose: () => {
console.log("Rating card closed");
},
// onSubmit: (rating) => {
// console.log("Rating submitted:", rating);
// },
// onClose: () => {
// console.log("Rating card closed");
// },
storeListingVersionId: "1",
},
};
export const WithoutCallbacks: Story = {
args: {
agentName: "Test Agent",
storeListingVersionId: "1",
},
};

View File

@@ -44,6 +44,9 @@ export const Rejected: Story = {
};
export const AllStatuses: Story = {
args: {
status: "draft" as StatusType,
},
render: () => (
<div className="flex flex-col gap-4">
<Status status="draft" />

View File

@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { AgentsSection } from "./AgentsSection";
import { Agent, AgentsSection } from "./AgentsSection";
import { userEvent, within, expect } from "@storybook/test";
const meta = {
@@ -16,7 +16,7 @@ const meta = {
argTypes: {
sectionTitle: { control: "text" },
agents: { control: "object" },
onCardClick: { action: "clicked" },
// onCardClick: { action: "clicked" },
},
} satisfies Meta<typeof AgentsSection>;
@@ -25,41 +25,50 @@ type Story = StoryObj<typeof meta>;
const mockTopAgents = [
{
agentName: "SEO Optimizer Pro",
agentImage:
agent_name: "SEO Optimizer Pro",
agent_image:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
description:
"Boost your website's search engine rankings with our advanced AI-powered SEO optimization tool.",
runs: 50000,
rating: 4.7,
avatarSrc: "https://example.com/avatar1.jpg",
creator_avatar: "https://example.com/avatar1.jpg",
slug: "seo-optimizer-pro",
creator: "John Doe",
sub_heading: "SEO Expert",
},
{
agentName: "Content Writer AI",
agentImage:
agent_name: "Content Writer AI",
agent_image:
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
description:
"Generate high-quality, engaging content for your blog, social media, or marketing campaigns.",
runs: 75000,
rating: 4.5,
avatarSrc: "https://example.com/avatar2.jpg",
creator_avatar: "https://example.com/avatar2.jpg",
slug: "content-writer-ai",
creator: "Jane Doe",
sub_heading: "Content Writer",
},
{
agentName: "Data Analyzer Lite",
agentImage:
agent_name: "Data Analyzer Lite",
agent_image:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
description: "A basic tool for analyzing small to medium-sized datasets.",
runs: 10000,
rating: 3.8,
avatarSrc: "https://example.com/avatar3.jpg",
creator_avatar: "https://example.com/avatar3.jpg",
slug: "data-analyzer-lite",
creator: "John Doe",
sub_heading: "Data Analyst",
},
];
] satisfies Agent[];
export const Default: Story = {
args: {
sectionTitle: "Top Agents",
agents: mockTopAgents,
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
};
@@ -67,7 +76,7 @@ export const SingleAgent: Story = {
args: {
sectionTitle: "Top Agents",
agents: [mockTopAgents[0]],
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
};
@@ -75,7 +84,7 @@ export const NoAgents: Story = {
args: {
sectionTitle: "Top Agents",
agents: [],
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
};
@@ -83,7 +92,7 @@ export const WithInteraction: Story = {
args: {
sectionTitle: "Top Agents",
agents: mockTopAgents,
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@@ -107,6 +116,9 @@ export const MultiRowAgents: Story = {
runs: 60000,
rating: 4.6,
creator_avatar: "https://example.com/avatar4.jpg",
slug: "image-recognition-ai",
creator: "John Doe",
sub_heading: "Image Recognition",
},
{
agent_name: "Natural Language Processor",
@@ -117,6 +129,9 @@ export const MultiRowAgents: Story = {
runs: 80000,
rating: 4.8,
creator_avatar: "https://example.com/avatar5.jpg",
slug: "natural-language-processor",
creator: "John Doe",
sub_heading: "Natural Language Processing",
},
{
agent_name: "Sentiment Analyzer",
@@ -127,6 +142,9 @@ export const MultiRowAgents: Story = {
runs: 45000,
rating: 4.3,
creator_avatar: "https://example.com/avatar6.jpg",
slug: "sentiment-analyzer",
creator: "John Doe",
sub_heading: "Sentiment Analysis",
},
{
agent_name: "Chatbot Builder",
@@ -137,6 +155,9 @@ export const MultiRowAgents: Story = {
runs: 55000,
rating: 4.4,
creator_avatar: "https://example.com/avatar7.jpg",
slug: "chatbot-builder",
creator: "John Doe",
sub_heading: "Chatbot Developer",
},
{
agent_name: "Predictive Analytics Tool",
@@ -147,6 +168,9 @@ export const MultiRowAgents: Story = {
runs: 40000,
rating: 4.2,
creator_avatar: "https://example.com/avatar8.jpg",
slug: "predictive-analytics-tool",
creator: "John Doe",
sub_heading: "Predictive Analytics",
},
{
agent_name: "Text-to-Speech Converter",
@@ -157,9 +181,12 @@ export const MultiRowAgents: Story = {
runs: 35000,
rating: 4.1,
creator_avatar: "https://example.com/avatar9.jpg",
slug: "text-to-speech-converter",
creator: "John Doe",
sub_heading: "Text-to-Speech",
},
],
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
};

View File

@@ -8,6 +8,7 @@ import {
CarouselItem,
} from "@/components/ui/carousel";
import { useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
export interface Agent {
slug: string;

View File

@@ -15,7 +15,7 @@ const meta = {
tags: ["autodocs"],
argTypes: {
featuredCreators: { control: "object" },
onCardClick: { action: "cardClicked" },
// onCardClick: { action: "cardClicked" },
},
} satisfies Meta<typeof FeaturedCreators>;
@@ -64,14 +64,14 @@ const defaultCreators = [
export const Default: Story = {
args: {
featuredCreators: defaultCreators,
onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
// onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
},
};
export const SingleCreator: Story = {
args: {
featuredCreators: [defaultCreators[0]],
onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
// onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
},
};
@@ -98,14 +98,14 @@ export const ManyCreators: Story = {
num_agents: 25,
},
],
onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
// onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
},
};
export const WithInteraction: Story = {
args: {
featuredCreators: defaultCreators,
onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
// onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { FeaturedSection } from "./FeaturedSection";
import { FeaturedAgent, FeaturedSection } from "./FeaturedSection";
import { userEvent, within, expect } from "@storybook/test";
const meta = {
@@ -15,7 +15,7 @@ const meta = {
tags: ["autodocs"],
argTypes: {
featuredAgents: { control: "object" },
onCardClick: { action: "clicked" },
// onCardClick: { action: "clicked" },
},
} satisfies Meta<typeof FeaturedSection>;
@@ -24,97 +24,102 @@ type Story = StoryObj<typeof meta>;
const mockFeaturedAgents = [
{
agentName: "Personalized Morning Coffee Newsletter example of three lines",
subHeading:
agent_name: "Personalized Morning Coffee Newsletter example of three lines",
sub_heading:
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
creatorName: "AI Solutions Inc.",
creator: "AI Solutions Inc.",
description:
"Elevate your web content with this powerful AI Webpage Copy Improver. Designed for marketers, SEO specialists, and web developers, this tool analyses and enhances website copy for maximum impact. Using advanced language models, it optimizes text for better clarity, SEO performance, and increased conversion rates.",
runs: 50000,
rating: 4.7,
agentImage:
agent_image:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
creatorImage:
creator_avatar:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
slug: "personalized-morning-coffee-newsletter",
},
{
agentName: "Data Analyzer Lite",
subHeading: "Basic data analysis tool",
creatorName: "DataTech",
agent_name: "Data Analyzer Lite",
sub_heading: "Basic data analysis tool",
creator: "DataTech",
description:
"A lightweight data analysis tool for basic data processing needs.",
runs: 10000,
rating: 2.8,
agentImage:
agent_image:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
creatorImage:
creator_avatar:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
slug: "data-analyzer-lite",
},
{
agentName: "CodeAssist AI",
subHeading: "Your AI coding companion",
creatorName: "DevTools Co.",
agent_name: "CodeAssist AI",
sub_heading: "Your AI coding companion",
creator: "DevTools Co.",
description:
"An intelligent coding assistant that helps developers write better code faster.",
runs: 1000000,
rating: 4.9,
agentImage:
agent_image:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
creatorImage:
creator_avatar:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
slug: "codeassist-ai",
},
{
agentName: "MultiTasker",
subHeading: "All-in-one productivity suite",
creatorName: "Productivity Plus",
agent_name: "MultiTasker",
sub_heading: "All-in-one productivity suite",
creator: "Productivity Plus",
description:
"A comprehensive productivity suite that combines task management, note-taking, and project planning into one seamless interface. Features include smart task prioritization, automated scheduling, and AI-powered insights to help you work more efficiently.",
runs: 75000,
rating: 4.5,
agentImage:
agent_image:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
creatorImage:
creator_avatar:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
slug: "multitasker",
},
{
agentName: "QuickTask",
subHeading: "Fast task automation",
creatorName: "EfficientWorks",
agent_name: "QuickTask",
sub_heading: "Fast task automation",
creator: "EfficientWorks",
description: "Simple and efficient task automation tool.",
runs: 50000,
rating: 4.2,
agentImage:
agent_image:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
creatorImage:
creator_avatar:
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
slug: "quicktask",
},
];
] satisfies FeaturedAgent[];
export const Default: Story = {
args: {
featuredAgents: mockFeaturedAgents,
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
};
export const SingleAgent: Story = {
args: {
featuredAgents: [mockFeaturedAgents[0]],
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
};
export const NoAgents: Story = {
args: {
featuredAgents: [],
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
};
export const WithInteraction: Story = {
args: {
featuredAgents: mockFeaturedAgents,
onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@@ -8,7 +8,10 @@ import {
PopoverAnchor,
} from "@/components/ui/popover";
import { PublishAgentSelect } from "../PublishAgentSelect";
import { PublishAgentInfo } from "../PublishAgentSelectInfo";
import {
PublishAgentInfo,
PublishAgentInfoInitialData,
} from "../PublishAgentSelectInfo";
import { PublishAgentAwaitingReview } from "../PublishAgentAwaitingReview";
import { Button } from "../Button";
import {
@@ -45,17 +48,17 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
);
const [myAgents, setMyAgents] = React.useState<MyAgentsResponse | null>(null);
const [selectedAgent, setSelectedAgent] = React.useState<string | null>(null);
const [initialData, setInitialData] = React.useState<{
agent_id: string;
title: string;
subheader: string;
slug: string;
thumbnailSrc: string;
youtubeLink: string;
category: string;
description: string;
additionalImages?: string[];
} | null>(null);
const [initialData, setInitialData] =
React.useState<PublishAgentInfoInitialData>({
agent_id: "",
title: "",
subheader: "",
slug: "",
thumbnailSrc: "",
youtubeLink: "",
category: "",
description: "",
});
const [publishData, setPublishData] =
React.useState<StoreSubmissionRequest>(submissionData);
const [selectedAgentId, setSelectedAgentId] = React.useState<string | null>(

View File

@@ -2,16 +2,14 @@ import { LDProvider } from "launchdarkly-react-client-sdk";
import { ReactNode } from "react";
export function LaunchDarklyProvider({ children }: { children: ReactNode }) {
if (
process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === true &&
!process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID
) {
const clientId = process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID;
const enabled = process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === "true";
if (!enabled) return <>{children}</>;
if (!clientId) {
throw new Error("NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID is not defined");
}
return (
<LDProvider clientSideID={process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID}>
{children}
</LDProvider>
);
return <LDProvider clientSideID={clientId}>{children}</LDProvider>;
}

View File

@@ -41,7 +41,6 @@ import { LocalValuedInput } from "./ui/input";
import NodeHandle from "./NodeHandle";
import { ConnectionData } from "./CustomNode";
import { CredentialsInput } from "./integrations/credentials-input";
import { MultiSelect } from "./ui/multiselect-input";
type NodeObjectInputTreeProps = {
nodeId: string;
@@ -663,20 +662,6 @@ export const NodeGenericInputField: FC<{
handleInputClick={handleInputClick}
/>
);
case "object":
return (
<NodeKeyValueInput
nodeId={nodeId}
selfKey={propKey}
schema={propSchema}
entries={currentValue}
errors={errors}
className={className}
displayName={displayName}
connections={connections}
handleInputChange={handleInputChange}
/>
);
default:
console.warn(
`Schema for '${propKey}' specifies unknown type:`,
@@ -968,10 +953,8 @@ const NodeKeyValueInput: FC<{
>
<div>
{keyValuePairs.map(({ key, value }, index) => (
/*
The `index` is used as a DOM key instead of the actual `key`
because the `key` can change with each input, causing the input to lose focus.
*/
// The `index` is used as a DOM key instead of the actual `key`
// because the `key` can change with each input, causing the input to lose focus.
<div key={index}>
<NodeHandle
keyName={getEntryKey(key)}

View File

@@ -1,6 +1,14 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { useForm } from "react-hook-form";
import {
FieldValues,
InternalFieldName,
RegisterOptions,
useForm,
UseFormHandleSubmit,
UseFormRegister,
UseFormWatch,
} from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -72,10 +80,300 @@ const FormExample = () => {
};
export const Default: Story = {
args: {
children: <FormExample />,
// watch(callback: (data, { name, type }) => void, defaultValues?: {[key:string]: unknown}): { unsubscribe: () => void }
watch: (name?: any, defaultValue?: any) => {
if (typeof name === "function") {
return { unsubscribe: () => {} };
}
return defaultValue || {};
},
getValues: () => [],
getFieldState: (name, formState) => ({
invalid: false,
isDirty: false,
isTouched: false,
isValidating: false,
error: undefined,
}),
setError: () => {},
setValue: () => {},
trigger: async () => true,
reset: () => {},
clearErrors: () => {},
formState: {
errors: {},
isDirty: false,
isSubmitting: false,
isValid: true,
isLoading: false,
isSubmitted: false,
isSubmitSuccessful: false,
isValidating: false,
defaultValues: {},
dirtyFields: {},
touchedFields: {},
disabled: false,
submitCount: 0,
validatingFields: {},
},
resetField: () => {},
handleSubmit: (() => {
return async (e?: React.BaseSyntheticEvent) => {
e?.preventDefault();
return Promise.resolve();
};
}) as unknown as UseFormHandleSubmit<any>,
unregister: () => {},
control: {
_subjects: {
state: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
array: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
values: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
},
_reset: () => {},
_resetDefaultValues: () => {},
_getFieldArray: () => [],
_setErrors: () => {},
_updateDisabledField: () => {},
_executeSchema: () => Promise.resolve({ errors: {} }),
handleSubmit: (onSubmit?: any) => (e?: React.BaseSyntheticEvent) => {
e?.preventDefault();
return Promise.resolve();
},
unregister: () => {},
getFieldState: () => ({
invalid: false,
isDirty: false,
isTouched: false,
isValidating: false,
error: undefined,
}),
setError: () => {},
_disableForm: () => {},
_removeUnmounted: () => {},
_names: {
mount: new Set(),
array: new Set(),
watch: new Set(),
unMount: new Set(),
disabled: new Set(),
},
_state: { mount: false, watch: false, action: false },
_options: { mode: "onSubmit", defaultValues: {} },
_formState: {
isDirty: false,
isSubmitted: false,
submitCount: 0,
isLoading: false,
isSubmitSuccessful: false,
isSubmitting: false,
isValidating: false,
isValid: true,
disabled: false,
dirtyFields: {},
touchedFields: {},
errors: {},
validatingFields: {},
},
_fields: {},
_defaultValues: {},
_formValues: {},
_proxyFormState: {
isDirty: false,
dirtyFields: false,
touchedFields: false,
errors: false,
isValid: true,
isValidating: false,
validatingFields: false,
},
_getDirty: () => false,
_updateValid: () => {},
_updateFieldArray: () => {},
_getWatch: () => ({}),
_updateFormState: () => {},
register: ((name: string, options?: RegisterOptions<any>) => ({
name,
onChange: (e: any) => Promise.resolve(),
onBlur: (e: any) => Promise.resolve(),
ref: () => {},
})) as unknown as UseFormRegister<any>,
},
register: ((name: string) => ({
name,
onChange: (e: any) => Promise.resolve(),
onBlur: (e: any) => Promise.resolve(),
ref: () => {},
})) as UseFormRegister<FieldValues>,
setFocus: () => {},
},
render: () => <FormExample />,
};
export const WithError: Story = {
args: {
children: <FormExample />,
// watch(callback: (data, { name, type }) => void, defaultValues?: {[key:string]: unknown}): { unsubscribe: () => void }
watch: (name?: any, defaultValue?: any) => {
if (typeof name === "function") {
return { unsubscribe: () => {} };
}
return defaultValue || {};
},
getValues: () => [],
getFieldState: (name, formState) => ({
invalid: false,
isDirty: false,
isTouched: false,
isValidating: false,
error: undefined,
}),
setError: () => {},
setValue: () => {},
trigger: async () => true,
reset: () => {},
clearErrors: () => {},
formState: {
errors: {},
isDirty: false,
isSubmitting: false,
isValid: true,
isLoading: false,
isSubmitted: false,
isSubmitSuccessful: false,
isValidating: false,
defaultValues: {},
dirtyFields: {},
touchedFields: {},
disabled: false,
submitCount: 0,
validatingFields: {},
},
resetField: () => {},
handleSubmit: (() => {
return async (e?: React.BaseSyntheticEvent) => {
e?.preventDefault();
return Promise.resolve();
};
}) as unknown as UseFormHandleSubmit<any>,
unregister: () => {},
control: {
_subjects: {
state: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
array: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
values: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
},
_reset: () => {},
_resetDefaultValues: () => {},
_getFieldArray: () => [],
_setErrors: () => {},
_updateDisabledField: () => {},
_executeSchema: () => Promise.resolve({ errors: {} }),
handleSubmit: (onSubmit?: any) => (e?: React.BaseSyntheticEvent) => {
e?.preventDefault();
return Promise.resolve();
},
unregister: () => {},
getFieldState: () => ({
invalid: false,
isDirty: false,
isTouched: false,
isValidating: false,
error: undefined,
}),
setError: () => {},
_disableForm: () => {},
_removeUnmounted: () => {},
_names: {
mount: new Set(),
array: new Set(),
watch: new Set(),
unMount: new Set(),
disabled: new Set(),
},
_state: { mount: false, watch: false, action: false },
_options: { mode: "onSubmit", defaultValues: {} },
_formState: {
isDirty: false,
isSubmitted: false,
submitCount: 0,
isLoading: false,
isSubmitSuccessful: false,
isSubmitting: false,
isValidating: false,
isValid: true,
disabled: false,
dirtyFields: {},
touchedFields: {},
errors: {},
validatingFields: {},
},
_fields: {},
_defaultValues: {},
_formValues: {},
_proxyFormState: {
isDirty: false,
dirtyFields: false,
touchedFields: false,
errors: false,
isValid: true,
isValidating: false,
validatingFields: false,
},
_getDirty: () => false,
_updateValid: () => {},
_updateFieldArray: () => {},
_getWatch: () => ({}),
_updateFormState: () => {},
register: ((name: string, options?: RegisterOptions<any>) => ({
name,
onChange: (e: any) => Promise.resolve(),
onBlur: (e: any) => Promise.resolve(),
ref: () => {},
})) as unknown as UseFormRegister<any>,
},
register: ((name: string) => ({
name,
onChange: (e: any) => Promise.resolve(),
onBlur: (e: any) => Promise.resolve(),
ref: () => {},
})) as UseFormRegister<FieldValues>,
setFocus: () => {},
},
render: () => {
const FormWithError = () => {
const form = useForm<z.infer<typeof formSchema>>({
@@ -126,6 +424,151 @@ export const WithError: Story = {
};
export const WithDefaultValue: Story = {
args: {
children: <FormExample />,
// watch(callback: (data, { name, type }) => void, defaultValues?: {[key:string]: unknown}): { unsubscribe: () => void }
watch: (name?: any, defaultValue?: any) => {
if (typeof name === "function") {
return { unsubscribe: () => {} };
}
return defaultValue || {};
},
getValues: () => [],
getFieldState: (name, formState) => ({
invalid: false,
isDirty: false,
isTouched: false,
isValidating: false,
error: undefined,
}),
setError: () => {},
setValue: () => {},
trigger: async () => true,
reset: () => {},
clearErrors: () => {},
formState: {
errors: {},
isDirty: false,
isSubmitting: false,
isValid: true,
isLoading: false,
isSubmitted: false,
isSubmitSuccessful: false,
isValidating: false,
defaultValues: {},
dirtyFields: {},
touchedFields: {},
disabled: false,
submitCount: 0,
validatingFields: {},
},
resetField: () => {},
handleSubmit: (() => {
return async (e?: React.BaseSyntheticEvent) => {
e?.preventDefault();
return Promise.resolve();
};
}) as unknown as UseFormHandleSubmit<any>,
unregister: () => {},
control: {
_subjects: {
state: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
array: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
values: {
observers: [],
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
next: () => {},
},
},
_reset: () => {},
_resetDefaultValues: () => {},
_getFieldArray: () => [],
_setErrors: () => {},
_updateDisabledField: () => {},
_executeSchema: () => Promise.resolve({ errors: {} }),
handleSubmit: (onSubmit?: any) => (e?: React.BaseSyntheticEvent) => {
e?.preventDefault();
return Promise.resolve();
},
unregister: () => {},
getFieldState: () => ({
invalid: false,
isDirty: false,
isTouched: false,
isValidating: false,
error: undefined,
}),
setError: () => {},
_disableForm: () => {},
_removeUnmounted: () => {},
_names: {
mount: new Set(),
array: new Set(),
watch: new Set(),
unMount: new Set(),
disabled: new Set(),
},
_state: { mount: false, watch: false, action: false },
_options: { mode: "onSubmit", defaultValues: {} },
_formState: {
isDirty: false,
isSubmitted: false,
submitCount: 0,
isLoading: false,
isSubmitSuccessful: false,
isSubmitting: false,
isValidating: false,
isValid: true,
disabled: false,
dirtyFields: {},
touchedFields: {},
errors: {},
validatingFields: {},
},
_fields: {},
_defaultValues: {},
_formValues: {},
_proxyFormState: {
isDirty: false,
dirtyFields: false,
touchedFields: false,
errors: false,
isValid: true,
isValidating: false,
validatingFields: false,
},
_getDirty: () => false,
_updateValid: () => {},
_updateFieldArray: () => {},
_getWatch: () => ({}),
_updateFormState: () => {},
register: ((name: string, options?: RegisterOptions<any>) => ({
name,
onChange: (e: any) => Promise.resolve(),
onBlur: (e: any) => Promise.resolve(),
ref: () => {},
})) as unknown as UseFormRegister<any>,
},
register: ((name: string) => ({
name,
onChange: (e: any) => Promise.resolve(),
onBlur: (e: any) => Promise.resolve(),
ref: () => {},
})) as UseFormRegister<FieldValues>,
setFocus: () => {},
},
render: () => {
const FormWithDefaultValue = () => {
const form = useForm<z.infer<typeof formSchema>>({

View File

@@ -7,7 +7,13 @@ import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = ({ children, delayDuration = 10 }) => (
const Tooltip = ({
children,
delayDuration = 10,
}: {
children: React.ReactNode;
delayDuration?: number;
}) => (
<TooltipPrimitive.Root delayDuration={delayDuration}>
{children}
</TooltipPrimitive.Root>

View File

@@ -1,6 +1,11 @@
import { useCallback } from "react";
import { Node, Edge, useReactFlow, useViewport } from "@xyflow/react";
interface CopyableData {
nodes: Node[];
edges: Edge[];
}
export function useCopyPaste(getNextNodeId: () => string) {
const { setNodes, addEdges, getNodes, getEdges } = useReactFlow();
const { x, y, zoom } = useViewport();
@@ -20,7 +25,7 @@ export function useCopyPaste(getNextNodeId: () => string) {
selectedNodeIds.has(edge.target),
);
const copiedData = {
const copiedData: CopyableData = {
nodes: selectedNodes.map((node) => ({
...node,
data: {
@@ -36,7 +41,7 @@ export function useCopyPaste(getNextNodeId: () => string) {
if (event.key === "v" || event.key === "V") {
const copiedDataString = localStorage.getItem("copiedFlowData");
if (copiedDataString) {
const copiedData = JSON.parse(copiedDataString);
const copiedData = JSON.parse(copiedDataString) as CopyableData;
const oldToNewIdMap: Record<string, string> = {};
const viewportCenter = {
@@ -77,7 +82,7 @@ export function useCopyPaste(getNextNodeId: () => string) {
};
});
const pastedEdges = copiedData.edges.map((edge: Edge) => {
const pastedEdges = copiedData.edges.map((edge) => {
const newSourceId = oldToNewIdMap[edge.source] ?? edge.source;
const newTargetId = oldToNewIdMap[edge.target] ?? edge.target;
return {
@@ -99,10 +104,10 @@ export function useCopyPaste(getNextNodeId: () => string) {
if (oldToNewIdMap[node.id]) {
const nodeConnections = pastedEdges
.filter(
(edge) =>
(edge: Edge) =>
edge.source === node.id || edge.target === node.id,
)
.map((edge) => ({
.map((edge: Edge) => ({
edge_id: edge.id,
source: edge.source,
target: edge.target,

View File

@@ -65,18 +65,21 @@ export type BlockIOObjectSubSchema = BlockIOSubSchemaMeta & {
properties: { [key: string]: BlockIOSubSchema };
default?: { [key: keyof BlockIOObjectSubSchema["properties"]]: any };
required?: (keyof BlockIOObjectSubSchema["properties"])[];
secret?: boolean;
};
export type BlockIOKVSubSchema = BlockIOSubSchemaMeta & {
type: "object";
additionalProperties: { type: "string" | "number" | "integer" };
default?: { [key: string]: string | number };
secret?: boolean;
};
export type BlockIOArraySubSchema = BlockIOSubSchemaMeta & {
type: "array";
items?: BlockIOSimpleTypeSubSchema;
default?: Array<string>;
secret?: boolean;
};
export type BlockIOStringSubSchema = BlockIOSubSchemaMeta & {
@@ -90,11 +93,13 @@ export type BlockIOStringSubSchema = BlockIOSubSchemaMeta & {
export type BlockIONumberSubSchema = BlockIOSubSchemaMeta & {
type: "integer" | "number";
default?: number;
secret?: boolean;
};
export type BlockIOBooleanSubSchema = BlockIOSubSchemaMeta & {
type: "boolean";
default?: boolean;
secret?: boolean;
};
export type CredentialsType = "api_key" | "oauth2" | "user_password";
@@ -143,16 +148,19 @@ export type CredentialsProviderName =
(typeof PROVIDER_NAMES)[keyof typeof PROVIDER_NAMES];
export type BlockIOCredentialsSubSchema = BlockIOSubSchemaMeta & {
type: "object";
/* Mirror of backend/data/model.py:CredentialsFieldSchemaExtra */
credentials_provider: CredentialsProviderName[];
credentials_scopes?: string[];
credentials_types: Array<CredentialsType>;
discriminator?: string;
discriminator_mapping?: { [key: string]: CredentialsProviderName };
secret?: boolean;
};
export type BlockIONullSubSchema = BlockIOSubSchemaMeta & {
type: "null";
secret?: boolean;
};
// At the time of writing, combined schemas only occur on the first nested level in a
@@ -160,15 +168,21 @@ export type BlockIONullSubSchema = BlockIOSubSchemaMeta & {
type BlockIOCombinedTypeSubSchema = BlockIOSubSchemaMeta &
(
| {
type: "allOf";
allOf: [BlockIOSimpleTypeSubSchema];
secret?: boolean;
}
| {
type: "anyOf";
anyOf: BlockIOSimpleTypeSubSchema[];
default?: string | number | boolean | null;
secret?: boolean;
}
| {
type: "oneOf";
oneOf: BlockIOSimpleTypeSubSchema[];
default?: string | number | boolean | null;
secret?: boolean;
}
);

View File

@@ -1,6 +1,5 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { faker } from "@faker-js/faker";
export type TestUser = {
email: string;
@@ -26,24 +25,6 @@ function getSupabaseAdmin() {
return supabase;
}
async function createTestUser(userData: TestUser): Promise<TestUser> {
const supabase = getSupabaseAdmin();
const { data: authUser, error: authError } = await supabase.auth.signUp({
email: userData.email,
password: userData.password,
});
if (authError) {
throw new Error(`Failed to create test user: ${authError.message}`);
}
return {
...userData,
id: authUser.user?.id,
};
}
async function deleteTestUser(userId: string) {
const supabase = getSupabaseAdmin();
@@ -59,25 +40,3 @@ async function deleteTestUser(userId: string) {
);
}
}
function generateUserData(): TestUser {
return {
email: `test.${faker.string.uuid()}@example.com`,
password: faker.internet.password({ length: 12 }),
};
}
// Export just the fixture function
export const createTestUserFixture = async ({}, use) => {
let user: TestUser | null = null;
try {
const userData = generateUserData();
user = await createTestUser(userData);
await use(user);
} finally {
if (user?.id) {
await deleteTestUser(user.id);
}
}
};