diff --git a/src/styles/globals.css b/src/styles/globals.css index 9cf9b70f..09f5cd57 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -173,6 +173,49 @@ } } +.generation-loading-sweep { + animation: generation-loading-sweep 1.8s ease-in-out infinite; +} + +.generation-loading-float { + animation: generation-loading-float 2.4s ease-in-out infinite; +} + +.generation-loading-pan { + animation: generation-loading-pan 1.6s ease-in-out infinite; +} + +@keyframes generation-loading-sweep { + 0% { + transform: translateX(-60%); + } + 100% { + transform: translateX(360%); + } +} + +@keyframes generation-loading-float { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-2px); + } +} + +@keyframes generation-loading-pan { + 0%, + 100% { + transform: translateX(-20%); + opacity: 0.55; + } + 50% { + transform: translateX(70%); + opacity: 1; + } +} + body { font-family: var(--font-sans); } diff --git a/src/toolkits/toolkits/components/generation-loading.tsx b/src/toolkits/toolkits/components/generation-loading.tsx new file mode 100644 index 00000000..e6616656 --- /dev/null +++ b/src/toolkits/toolkits/components/generation-loading.tsx @@ -0,0 +1,132 @@ +import { ImageIcon, Sparkles, VideoIcon } from "lucide-react"; +import React from "react"; + +import { cn } from "@/lib/utils"; + +type GenerationKind = "image" | "video"; + +type GenerationLoadingProps = { + kind: GenerationKind; + prompt?: string; + isPartial?: boolean; +}; + +const previewTiles = Array.from({ length: 9 }, (_, index) => index); +const videoFrames = Array.from({ length: 5 }, (_, index) => index); +const progressSegments = Array.from({ length: 3 }, (_, index) => index); + +export const GenerationLoading = ({ + kind, + prompt, + isPartial, +}: GenerationLoadingProps) => { + const isVideo = kind === "video"; + const Icon = isVideo ? VideoIcon : ImageIcon; + const trimmedPrompt = prompt?.trim(); + const promptText = + trimmedPrompt === undefined || trimmedPrompt.length === 0 + ? "Waiting for generation details" + : trimmedPrompt; + + return ( +
+
+
+
+ {isVideo ? : } +
+
+ +
+
+ +
+
+ +
+

+ {isPartial + ? "Preparing prompt" + : isVideo + ? "Rendering video" + : "Composing image"} +

+

+ {promptText} +

+
+
+ + +
+
+ ); +}; + +const ImageLoadingPreview = () => { + return ( +
+ {previewTiles.map((tile) => ( +
+ ))} +
+ ); +}; + +const VideoLoadingPreview = () => { + return ( +
+
+ {videoFrames.map((frame) => ( +
+ ))} +
+
+ {videoFrames.concat(videoFrames).map((frame, index) => ( +
+ ))} +
+
+ ); +}; diff --git a/src/toolkits/toolkits/image/tools/generate/client.tsx b/src/toolkits/toolkits/image/tools/generate/client.tsx index 7325a6b5..aa1cab31 100644 --- a/src/toolkits/toolkits/image/tools/generate/client.tsx +++ b/src/toolkits/toolkits/image/tools/generate/client.tsx @@ -2,6 +2,8 @@ import React from "react"; import Image from "next/image"; +import { GenerationLoading } from "../../../components/generation-loading"; + import type { baseGenerateTool } from "./base"; import type { ClientToolConfig } from "@/toolkits/types"; @@ -10,8 +12,14 @@ export const generateToolConfigClient: ClientToolConfig< typeof baseGenerateTool.inputSchema.shape, typeof baseGenerateTool.outputSchema.shape > = { - CallComponent: ({ args }) => { - return {args.prompt}; + CallComponent: ({ args, isPartial }) => { + return ( + + ); }, ResultComponent: ({ result }) => { return ( diff --git a/src/toolkits/toolkits/video/tools/generate/client.tsx b/src/toolkits/toolkits/video/tools/generate/client.tsx index 9c9bcf1c..907996f9 100644 --- a/src/toolkits/toolkits/video/tools/generate/client.tsx +++ b/src/toolkits/toolkits/video/tools/generate/client.tsx @@ -1,5 +1,7 @@ import React from "react"; +import { GenerationLoading } from "../../../components/generation-loading"; + import type { baseGenerateTool } from "./base"; import type { ClientToolConfig } from "@/toolkits/types"; @@ -8,8 +10,14 @@ export const generateToolConfigClient: ClientToolConfig< typeof baseGenerateTool.inputSchema.shape, typeof baseGenerateTool.outputSchema.shape > = { - CallComponent: ({ args }) => { - return {args.prompt}; + CallComponent: ({ args, isPartial }) => { + return ( + + ); }, ResultComponent: ({ result }) => { return