Merge branch 'swiftyos/sdk' into swiftyos/integrations

This commit is contained in:
SwiftyOS
2025-07-10 10:39:28 +02:00
38 changed files with 432 additions and 429 deletions

View File

@@ -345,19 +345,11 @@ class CredentialsMetaInput(BaseModel, Generic[CP, CT]):
@classmethod
def allowed_providers(cls) -> tuple[ProviderName, ...] | None:
args = get_args(cls.model_fields["provider"].annotation)
# If no type parameters are provided, allow any provider
if not args:
return None # None means no specific providers, allow any
return args
return get_args(cls.model_fields["provider"].annotation)
@classmethod
def allowed_cred_types(cls) -> tuple[CredentialsType, ...]:
args = get_args(cls.model_fields["type"].annotation)
# If no type parameters are provided, allow any credential type
if not args:
return ("api_key", "oauth2", "user_password") # All credential types
return args
return get_args(cls.model_fields["type"].annotation)
@classmethod
def validate_credentials_field_schema(cls, model: type["BlockSchema"]):

View File

@@ -127,7 +127,7 @@ class LibraryAgent(pydantic.BaseModel):
description=graph.description,
input_schema=graph.input_schema,
credentials_input_schema=(
graph.credentials_input_schema if sub_graphs else None
graph.credentials_input_schema if sub_graphs is not None else None
),
has_external_trigger=graph.has_webhook_trigger,
trigger_setup_info=(

View File

@@ -87,6 +87,7 @@
"react-shepherd": "6.1.8",
"recharts": "2.15.3",
"shepherd.js": "14.5.0",
"sonner": "2.0.6",
"tailwind-merge": "2.6.0",
"tailwindcss-animate": "1.0.7",
"uuid": "11.1.0",

View File

@@ -191,6 +191,9 @@ importers:
shepherd.js:
specifier: 14.5.0
version: 14.5.0
sonner:
specifier: 2.0.6
version: 2.0.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
tailwind-merge:
specifier: 2.6.0
version: 2.6.0
@@ -6376,6 +6379,12 @@ packages:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
sonner@2.0.6:
resolution: {integrity: sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -14194,6 +14203,11 @@ snapshots:
slash@3.0.0: {}
sonner@2.0.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
source-map-js@1.2.1: {}
source-map-support@0.5.21:

View File

@@ -11,7 +11,7 @@ import StarRating from "@/components/onboarding/StarRating";
import SchemaTooltip from "@/components/SchemaTooltip";
import { TypeBasedInput } from "@/components/type-based-input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { GraphMeta, StoreAgentDetails } from "@/lib/autogpt-server-api";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { cn } from "@/lib/utils";

View File

@@ -41,7 +41,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import LoadingBox, { LoadingSpinner } from "@/components/ui/loading";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
export default function AgentRunsPage(): React.ReactElement {
const { id: agentID }: { id: LibraryAgentID } = useParams();

View File

@@ -4,7 +4,7 @@ import { z } from "zod";
import { uploadAgentFormSchema } from "./LibraryUploadAgentDialog";
import { usePostV1CreateNewGraph } from "@/app/api/__generated__/endpoints/graphs/graphs";
import { GraphModel } from "@/app/api/__generated__/models/graphModel";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useState } from "react";
import { Graph } from "@/app/api/__generated__/models/graph";
import { sanitizeImportedGraph } from "@/lib/autogpt-server-api";

View File

@@ -5,7 +5,7 @@ import {
useGetV1ListUserApiKeys,
} from "@/app/api/__generated__/endpoints/api-keys/api-keys";
import { APIKeyWithoutHash } from "@/app/api/__generated__/models/aPIKeyWithoutHash";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { getQueryClient } from "@/lib/react-query/queryClient";
export const useAPISection = () => {

View File

@@ -5,7 +5,7 @@ import {
} from "@/app/api/__generated__/endpoints/api-keys/api-keys";
import { APIKeyPermission } from "@/app/api/__generated__/models/aPIKeyPermission";
import { CreateAPIKeyResponse } from "@/app/api/__generated__/models/createAPIKeyResponse";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { getQueryClient } from "@/lib/react-query/queryClient";
import { useState } from "react";

View File

@@ -4,7 +4,10 @@ import { Button } from "@/components/ui/button";
import useCredits from "@/hooks/useCredits";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { useSearchParams, useRouter } from "next/navigation";
import { useToast, useToastOnFail } from "@/components/ui/use-toast";
import {
useToast,
useToastOnFail,
} from "@/components/molecules/Toast/use-toast";
import { RefundModal } from "./RefundModal";
import { CreditTransaction } from "@/lib/autogpt-server-api";

View File

@@ -2,7 +2,7 @@
import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { IconKey, IconUser } from "@/components/ui/icons";
import { Trash2Icon } from "lucide-react";
import { KeyIcon } from "@phosphor-icons/react/dist/ssr";

View File

@@ -4,7 +4,7 @@ import { createDefaultValues, formSchema } from "./helper";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { updateSettings } from "../../actions";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { NotificationPreference } from "@/app/api/__generated__/models/notificationPreference";
import { User } from "@supabase/supabase-js";

View File

@@ -18,7 +18,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import LoadingBox from "@/components/ui/loading";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useTurnstile } from "@/hooks/useTurnstile";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { getBehaveAs } from "@/lib/utils";

View File

@@ -1,13 +1,13 @@
import React from "react";
import type { Metadata } from "next";
import { fonts } from "@/components/styles/fonts";
import type { Metadata } from "next";
import React from "react";
import "./globals.css";
import { Toaster } from "@/components/ui/toaster";
import { Providers } from "@/app/providers";
import TallyPopupSimple from "@/components/TallyPopup";
import { GoogleAnalytics } from "@/components/analytics/google-analytics";
import { Toaster } from "@/components/molecules/Toast/toaster";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
export const metadata: Metadata = {

View File

@@ -11,7 +11,7 @@ import {
TableHeader,
TableRow,
} from "./ui/table";
import { useToast } from "./ui/use-toast";
import { useToast } from "./molecules/Toast/use-toast";
type DataTableProps = {
title?: string;

View File

@@ -56,7 +56,7 @@ import RunnerUIWrapper, {
import { CronSchedulerDialog } from "@/components/cron-scheduler-dialog";
import PrimaryActionBar from "@/components/PrimaryActionButton";
import OttoChatWidget from "@/components/OttoChatWidget";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useCopyPaste } from "../hooks/useCopyPaste";
// This is for the history, this is the minimum distance a block must move before it is logged

View File

@@ -3,7 +3,7 @@ import { Button } from "./ui/button";
import { Textarea } from "./ui/textarea";
import { Maximize2, Minimize2, Clipboard } from "lucide-react";
import { createPortal } from "react-dom";
import { toast } from "./ui/use-toast";
import { toast } from "./molecules/Toast/use-toast";
interface ModalProps {
isOpen: boolean;

View File

@@ -18,7 +18,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { IconRefresh, IconSquare } from "@/components/ui/icons";
import { Input } from "@/components/ui/input";
import LoadingBox from "@/components/ui/loading";
import { useToastOnFail } from "@/components/ui/use-toast";
import { useToastOnFail } from "@/components/molecules/Toast/use-toast";
import {
AgentRunStatus,

View File

@@ -22,7 +22,10 @@ import { TypeBasedInput } from "@/components/type-based-input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { IconCross, IconPlay, IconSave } from "@/components/ui/icons";
import { Input } from "@/components/ui/input";
import { useToast, useToastOnFail } from "@/components/ui/use-toast";
import {
useToast,
useToastOnFail,
} from "@/components/molecules/Toast/use-toast";
import { isEmpty } from "lodash";
import { CalendarClockIcon, Trash2Icon } from "lucide-react";

View File

@@ -16,7 +16,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { IconCross } from "@/components/ui/icons";
import { Input } from "@/components/ui/input";
import LoadingBox from "@/components/ui/loading";
import { useToastOnFail } from "@/components/ui/use-toast";
import { useToastOnFail } from "@/components/molecules/Toast/use-toast";
import { humanizeCronExpression } from "@/lib/cron-expression-utils";
import { PlayIcon } from "lucide-react";

View File

@@ -5,7 +5,7 @@ import { Separator } from "@/components/ui/separator";
import BackendAPI, { LibraryAgent } from "@/lib/autogpt-server-api";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useOnboarding } from "../onboarding/onboarding-provider";
import { User } from "@supabase/supabase-js";

View File

@@ -6,7 +6,7 @@ import * as Sentry from "@sentry/nextjs";
import { useRouter } from "next/navigation";
import { useTransition } from "react";
import { LoadingSpinner } from "../ui/loading";
import { toast } from "../ui/use-toast";
import { toast } from "../molecules/Toast/use-toast";
export function ProfilePopoutMenuLogoutButton() {
const router = useRouter();

View File

@@ -5,7 +5,7 @@ import Image from "next/image";
import { Button } from "../agptui/Button";
import { IconCross, IconPlus } from "../ui/icons";
import BackendAPI from "@/lib/autogpt-server-api";
import { toast } from "../ui/use-toast";
import { toast } from "../molecules/Toast/use-toast";
export interface PublishAgentInfoInitialData {
agent_id: string;
@@ -133,6 +133,7 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
toast({
title: "Failed to upload image",
description: `Error: ${error}`,
variant: "destructive",
});
}
};

View File

@@ -13,7 +13,7 @@ import {
} from "@/components/ui/form";
import { Input } from "../ui/input";
import Link from "next/link";
import { useToast, useToastOnFail } from "../ui/use-toast";
import { useToast, useToastOnFail } from "../molecules/Toast/use-toast";
import useCredits from "@/hooks/useCredits";
import { useCallback, useEffect, useState } from "react";
@@ -88,7 +88,10 @@ export default function WalletRefill() {
setIsLoading(true);
await updateAutoTopUpConfig(data.refillAmount * 100, data.threshold * 100)
.then(() => {
toast({ title: "Auto top-up config updated! 🎉" });
toast({
title: "Auto top-up config updated! 🎉",
variant: "success",
});
})
.catch(toastOnFail("update auto top-up config"));
setIsLoading(false);

View File

@@ -17,7 +17,7 @@ import { Button } from "../Button";
import { MyAgentsResponse } from "@/lib/autogpt-server-api";
import { useRouter } from "next/navigation";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
interface PublishAgentPopoutProps {
trigger?: React.ReactNode;

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { Separator } from "@/components/ui/separator";
import { CronScheduler } from "@/components/cron-scheduler";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";

View File

@@ -15,7 +15,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
interface SaveControlProps {
agentMeta: GraphMeta | null;

View File

@@ -10,7 +10,7 @@ import {
UserPasswordCredentials,
} from "@/lib/autogpt-server-api";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { useToastOnFail } from "@/components/ui/use-toast";
import { useToastOnFail } from "@/components/molecules/Toast/use-toast";
import { toDisplayName } from "@/components/integrations/helper";
type APIKeyCredentialsCreatable = Omit<

View File

@@ -0,0 +1,30 @@
/* Custom toast styling. It's here to override the Sonner default styles via CSS specificity */
html body .toastDefault,
html body .toastInfo {
background: #3e3e43 !important;
border-color: #3e3e43 !important;
}
html body .toastSuccess {
background: #45b56e !important;
border-color: #45b56e !important;
}
html body .toastWarning {
background: #e77b00 !important;
border-color: #e77b00 !important;
}
html body .toastError {
background: #f26969 !important;
border-color: #f26969 !important;
}
html body .toastTitle {
color: #fff !important;
font-weight: bold !important;
}
html body .toastDescription {
color: #fff !important;
}

View File

@@ -0,0 +1,197 @@
import { Button } from "@/components/atoms/Button/Button";
import { toast } from "@/components/molecules/Toast/use-toast";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Toaster } from "./toaster";
const meta = {
title: "Molecules/Toast",
component: Toaster,
parameters: {
layout: "fullscreen",
},
tags: ["autodocs"],
} satisfies Meta<typeof Toaster>;
export default meta;
type Story = StoryObj<typeof meta>;
// Helper component to demonstrate toast functionality
function ToastDemo() {
function handleDefaultToast() {
toast({
title: "Default Toast",
description: "This is a default toast message.",
});
}
function handleSuccessToast() {
toast({
title: "Success!",
description: "Your operation was completed successfully.",
variant: "success",
});
}
function handleDestructiveToast() {
toast({
title: "Error!",
description: "Something went wrong. Please try again.",
variant: "destructive",
});
}
function handleInfoToast() {
toast({
title: "Information",
description: "Here's some helpful information for you.",
variant: "info",
});
}
function handleToastWithAction() {
toast({
title: "Toast with Action",
description: "This toast has a custom action button.",
action: (
<Button variant="secondary" size="small">
Action
</Button>
),
});
}
function handlePersistentToast() {
toast({
title: "Persistent Toast",
description: "This toast won't auto-dismiss.",
dismissable: false,
});
}
function handleLongDurationToast() {
toast({
title: "Long Duration Toast",
description: "This toast stays visible for 10 seconds.",
duration: 10000,
});
}
return (
<div className="flex flex-col gap-4 p-8">
<h2 className="text-2xl font-bold">Toast Examples</h2>
<div className="flex flex-wrap gap-4">
<Button onClick={handleDefaultToast}>Default Toast</Button>
<Button onClick={handleSuccessToast}>Success Toast</Button>
<Button onClick={handleDestructiveToast}>Error Toast</Button>
<Button onClick={handleInfoToast}>Info Toast</Button>
<Button onClick={handleToastWithAction}>Toast with Action</Button>
<Button onClick={handlePersistentToast}>Persistent Toast</Button>
<Button onClick={handleLongDurationToast}>Long Duration Toast</Button>
</div>
<Toaster />
</div>
);
}
export const Default: Story = {
render: () => <ToastDemo />,
};
export const SuccessToast: Story = {
render: () => (
<div className="p-8">
<Button
onClick={() =>
toast({
title: "Success!",
description: "Your operation was completed successfully.",
variant: "success",
})
}
>
Show Success Toast
</Button>
<Toaster />
</div>
),
};
export const ErrorToast: Story = {
render: () => (
<div className="p-8">
<Button
onClick={() =>
toast({
title: "Error!",
description: "Something went wrong. Please try again.",
variant: "destructive",
})
}
>
Show Error Toast
</Button>
<Toaster />
</div>
),
};
export const InfoToast: Story = {
render: () => (
<div className="p-8">
<Button
onClick={() =>
toast({
title: "Information",
description: "Here's some helpful information for you.",
variant: "info",
})
}
>
Show Info Toast
</Button>
<Toaster />
</div>
),
};
export const ToastWithAction: Story = {
render: () => (
<div className="p-8">
<Button
onClick={() =>
toast({
title: "Toast with Action",
description: "This toast has a custom action button.",
action: (
<Button variant="secondary" size="small">
Action
</Button>
),
})
}
>
Show Toast with Action
</Button>
<Toaster />
</div>
),
};
export const PersistentToast: Story = {
render: () => (
<div className="p-8">
<Button
onClick={() =>
toast({
title: "Persistent Toast",
description: "This toast won't auto-dismiss. Click the X to close.",
dismissable: false,
})
}
>
Show Persistent Toast
</Button>
<Toaster />
</div>
),
};

View File

@@ -0,0 +1,31 @@
"use client";
import { Toaster as SonnerToaster } from "sonner";
import { CheckCircle, XCircle, Warning, Info } from "@phosphor-icons/react";
import styles from "./styles.module.css";
export function Toaster() {
return (
<SonnerToaster
position="bottom-center"
richColors
toastOptions={{
classNames: {
toast: styles.toastDefault,
title: styles.toastTitle,
description: styles.toastDescription,
error: styles.toastError,
success: styles.toastSuccess,
warning: styles.toastWarning,
info: styles.toastInfo,
},
}}
icons={{
success: <CheckCircle className="h-5 w-5" color="#fff" />,
error: <XCircle className="h-5 w-5" color="#fff" />,
warning: <Warning className="h-5 w-5" color="#fff" />,
info: <Info className="h-5 w-5" color="#fff" />,
}}
/>
);
}

View File

@@ -0,0 +1,116 @@
"use client";
import * as React from "react";
import { toast as sonnerToast } from "sonner";
export interface ToastProps {
title?: React.ReactNode;
description?: React.ReactNode;
variant?: "default" | "destructive" | "success" | "info";
duration?: number;
action?: React.ReactNode;
dismissable?: boolean;
}
type ToasterToast = ToastProps & {
id: string;
open?: boolean;
onOpenChange?: (open: boolean) => void;
};
interface State {
toasts: ToasterToast[];
}
type Toast = Omit<ToasterToast, "id">;
function toast({
title,
description,
variant = "default",
duration = 5000,
action,
dismissable = true,
..._props
}: Toast) {
const message = title || description || "";
const descriptionText = title && description ? description : undefined;
const toastOptions = {
duration: dismissable ? duration : Infinity,
action,
description: descriptionText,
};
let toastId: string | number;
switch (variant) {
case "destructive":
toastId = sonnerToast.error(message, toastOptions);
break;
case "success":
toastId = sonnerToast.success(message, toastOptions);
break;
case "info":
toastId = sonnerToast.info(message, toastOptions);
break;
default:
toastId = sonnerToast(message, toastOptions);
}
const id = toastId.toString();
const update = (newProps: ToasterToast) => {
sonnerToast.dismiss(toastId);
return toast(newProps);
};
const dismiss = () => sonnerToast.dismiss(toastId);
return {
id,
dismiss,
update,
};
}
function useToast() {
const [state] = React.useState<State>({ toasts: [] });
return {
...state,
toast,
dismiss: (toastId?: string) => {
if (toastId) {
sonnerToast.dismiss(toastId);
} else {
sonnerToast.dismiss();
}
},
};
}
interface ToastOnFailOptions {
rethrow?: boolean;
}
function useToastOnFail() {
return React.useCallback(
(action: string, { rethrow = false }: ToastOnFailOptions = {}) =>
(error: any) => {
const err = error as Error;
toast({
title: `Unable to ${action}`,
description: err.message ?? "Something went wrong",
variant: "destructive",
duration: 10000,
});
if (rethrow) {
throw error;
}
},
[],
);
}
export { toast, useToast, useToastOnFail };

View File

@@ -12,7 +12,7 @@ import {
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
import { ClockIcon, Loader2 } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { humanizeCronExpression } from "@/lib/cron-expression-utils";
import {
Select,

View File

@@ -11,7 +11,7 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Clipboard } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
export type BlockOutput = {
metadata: {

View File

@@ -1,130 +0,0 @@
"use client";
import * as React from "react";
import { Cross2Icon } from "@radix-ui/react-icons";
import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className,
)}
{...props}
/>
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
"whitespace-pre-line group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-neutral-200 p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full dark:border-neutral-800",
{
variants: {
variant: {
default:
"border bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50",
destructive:
"destructive group border-red-500 bg-red-500 text-neutral-50 dark:border-red-900 dark:bg-red-900 dark:text-neutral-50",
},
},
defaultVariants: {
variant: "default",
},
},
);
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
);
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border border-neutral-200 bg-transparent px-3 text-sm font-medium transition-colors hover:bg-neutral-100 focus:outline-none focus:ring-1 focus:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-neutral-100/40 group-[.destructive]:hover:border-red-500/30 group-[.destructive]:hover:bg-red-500 group-[.destructive]:hover:text-neutral-50 group-[.destructive]:focus:ring-red-500 dark:border-neutral-800 dark:hover:bg-neutral-800 dark:focus:ring-neutral-300 dark:group-[.destructive]:border-neutral-800/40 dark:group-[.destructive]:hover:border-red-900/30 dark:group-[.destructive]:hover:bg-red-900 dark:group-[.destructive]:hover:text-neutral-50 dark:group-[.destructive]:focus:ring-red-900",
className,
)}
{...props}
/>
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-1 top-1 rounded-md p-1 text-neutral-950/50 opacity-0 transition-opacity hover:text-neutral-950 focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:text-neutral-50/50 dark:hover:text-neutral-50",
className,
)}
toast-close=""
{...props}
>
<Cross2Icon className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
{...props}
/>
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
};

View File

@@ -1,43 +0,0 @@
"use client";
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast";
import { useToast } from "@/components/ui/use-toast";
export function Toaster() {
const { toasts } = useToast();
// This neat little feature makes the toaster buggy due to the following issue:
// https://github.com/radix-ui/primitives/issues/2233
// TODO: Re-enable when the above issue is fixed:
// const swipeThreshold = toasts.some((toast) => toast.dismissable === false)
// ? Infinity
// : undefined;
const swipeThreshold = undefined;
return (
<ToastProvider swipeThreshold={swipeThreshold}>
{toasts.map(
({ id, title, description, action, dismissable, ...props }) => (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
{dismissable !== false && <ToastClose />}
</Toast>
),
)}
<ToastViewport />
</ToastProvider>
);
}

View File

@@ -1,215 +0,0 @@
"use client";
// Inspired by react-hot-toast library
import * as React from "react";
import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & {
id: string;
title?: React.ReactNode;
description?: React.ReactNode;
action?: ToastActionElement;
dismissable?: boolean;
};
type ActionTypes = {
ADD_TOAST: "ADD_TOAST";
UPDATE_TOAST: "UPDATE_TOAST";
DISMISS_TOAST: "DISMISS_TOAST";
REMOVE_TOAST: "REMOVE_TOAST";
};
let count = 0;
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString();
}
type ActionType = ActionTypes;
type Action =
| {
type: ActionType["ADD_TOAST"];
toast: ToasterToast;
}
| {
type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast>;
}
| {
type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"];
}
| {
type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"];
};
interface State {
toasts: ToasterToast[];
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId);
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
});
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
};
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
};
case "DISMISS_TOAST": {
const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId);
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id);
});
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t,
),
};
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
};
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
};
}
};
const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] };
function dispatch(action: Action) {
memoryState = reducer(memoryState, action);
listeners.forEach((listener) => {
listener(memoryState);
});
}
type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) {
const id = genId();
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
});
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss();
},
},
});
return {
id: id,
dismiss,
update,
};
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => {
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1);
}
};
}, [state]);
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
};
}
interface ToastOnFailOptions {
rethrow?: boolean;
}
function useToastOnFail() {
return React.useCallback(
(action: string, { rethrow = false }: ToastOnFailOptions = {}) =>
(error: any) => {
const err = error as Error;
toast({
title: `Unable to ${action}`,
description: err.message ?? "Something went wrong",
variant: "destructive",
duration: 10000,
});
if (rethrow) {
throw error;
}
},
[],
);
}
export { toast, useToast, useToastOnFail };

View File

@@ -2,7 +2,7 @@ import { CustomEdge } from "@/components/CustomEdge";
import { CustomNode } from "@/components/CustomNode";
import { useOnboarding } from "@/components/onboarding/onboarding-provider";
import { InputItem } from "@/components/RunnerUIWrapper";
import { useToast } from "@/components/ui/use-toast";
import { useToast } from "@/components/molecules/Toast/use-toast";
import BackendAPI, {
Block,
BlockIOSubSchema,