@slopmachine/react 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/SlopImage.tsx","../src/components/SlopVideo.tsx","../src/components/SlopText.tsx","../src/index.ts"],"sourcesContent":["import React, { useMemo, useState } from \"react\";\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nimport {\n buildImageUrl,\n type SlopImageOptions,\n type ImageAspectRatio,\n} from \"@slopmachine/core\";\n\n// Utility for Tailwind classes\nfunction cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport interface SlopImageProps\n extends\n Omit<React.ImgHTMLAttributes<HTMLImageElement>, \"src\" | \"alt\">,\n Omit<SlopImageOptions, \"aspectRatio\"> {\n /**\n * The aspect ratio of the generated image. Defaults to \"1:1\".\n */\n aspectRatio?: ImageAspectRatio;\n /**\n * Custom React node to display while the image is loading.\n * If not provided, a default spinner and shimmer effect will be shown.\n */\n loader?: React.ReactNode;\n /**\n * How the image should be resized to fit its container. Defaults to \"cover\".\n */\n objectFit?: React.CSSProperties[\"objectFit\"];\n /**\n * Additional CSS classes to apply directly to the `<img />` element.\n */\n imageClassName?: string;\n}\n\n/**\n * Renders an image generated by Slop Machine.\n *\n * This component automatically constructs the correct URL for the Slop Machine API,\n * displays a loading skeleton or custom loader while fetching, and handles errors.\n *\n * @example\n * ```tsx\n * // Using a Bucket\n * <SlopImage\n * bucketId=\"my-bucket\"\n * resultId=\"some-result\"\n * aspectRatio=\"16:9\"\n * className=\"rounded-lg\"\n * objectFit=\"cover\"\n * />\n *\n * // Using a Pipeline with runtime prompt\n * <SlopImage\n * pipelineId=\"pipe_live_abc123\"\n * prompt=\"1960s retro robot eating pizza on Mars\"\n * aspectRatio=\"16:9\"\n * metadata={{ userId: \"123\" }}\n * />\n * ```\n *\n * @version 0.5.0\n */\nexport const SlopImage: React.FC<SlopImageProps> = ({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n className,\n aspectRatio = \"1:1\",\n version,\n resultId,\n variables = {},\n baseUrl,\n original,\n attachments,\n loader,\n objectFit = \"cover\",\n imageClassName,\n style,\n onLoad,\n onError,\n ...props\n}) => {\n // Construct API URL\n const rawSrc = useMemo(\n () =>\n buildImageUrl({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n aspectRatio,\n version,\n resultId,\n variables,\n baseUrl,\n original,\n attachments,\n }),\n [\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n aspectRatio,\n version,\n resultId,\n variables,\n baseUrl,\n original,\n attachments,\n ],\n );\n\n const [src, setSrc] = useState(rawSrc);\n\n React.useEffect(() => {\n const timer = setTimeout(() => {\n setSrc(rawSrc);\n }, 100);\n return () => clearTimeout(timer);\n }, [rawSrc]);\n\n const alt = \"Image produced by Slop Machine (slopmachine.dev)\";\n\n const [isLoading, setIsLoading] = useState(true);\n const [currentSrc, setCurrentSrc] = useState(src);\n\n if (src !== currentSrc) {\n setCurrentSrc(src);\n setIsLoading(true);\n }\n\n React.useEffect(() => {\n if (!src) return;\n\n fetch(src, { method: \"HEAD\" })\n .then(async (res) => {\n if (!res.ok) {\n let errorMessage = res.statusText;\n try {\n const errRes = await fetch(src);\n const errorText = await errRes.text();\n const errorData = JSON.parse(errorText);\n if (errorData.error) {\n errorMessage = errorData.error;\n }\n } catch (e) {\n // Failed to fetch or parse error details\n }\n console.error(\"SlopImage error:\", res.status, errorMessage);\n setIsLoading(false);\n }\n })\n .catch((err) => {\n console.error(`Error fetching image from ${src}:`, err);\n setIsLoading(false);\n });\n }, [src]);\n\n return (\n <>\n <style>{`\n @keyframes slop-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n }\n @keyframes slop-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n }\n `}</style>\n <div className={className} style={style}>\n <div\n className=\"slop-wrapper relative overflow-hidden w-full h-full\"\n style={{\n aspectRatio: String(aspectRatio).replace(\":\", \"/\"),\n position: \"relative\",\n overflow: \"hidden\",\n width: \"100%\",\n height: \"100%\",\n }}\n >\n {/* Loading UI */}\n {loader ? (\n <div\n className={cn(\n \"absolute inset-0 z-10 flex items-center justify-center transition-opacity duration-300\",\n isLoading ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n opacity: isLoading ? 1 : 0,\n pointerEvents: isLoading ? \"auto\" : \"none\",\n transition: \"opacity 300ms ease-in-out\",\n }}\n >\n {loader}\n </div>\n ) : (\n <>\n {/* Loading overlay */}\n <div\n className={cn(\n \"absolute inset-0 z-10 flex items-center justify-center bg-muted transition-opacity duration-300\",\n isLoading ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"var(--muted, #f3f4f6)\",\n opacity: isLoading ? 1 : 0,\n pointerEvents: isLoading ? \"auto\" : \"none\",\n transition: \"opacity 300ms ease-in-out\",\n }}\n >\n <div\n className=\"flex flex-col items-center gap-2\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n }}\n >\n <svg\n className=\"spinner size-6 text-muted-foreground\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n style={{\n width: \"24px\",\n height: \"24px\",\n color: \"var(--muted-foreground, #6b7280)\",\n animation: \"slop-spin 1s linear infinite\",\n }}\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n style={{ opacity: 0.25 }}\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n style={{ opacity: 0.75 }}\n />\n </svg>\n <span\n className=\"text-xs text-muted-foreground\"\n style={{\n fontSize: \"0.75rem\",\n color: \"var(--muted-foreground, #6b7280)\",\n }}\n >\n Loading...\n </span>\n </div>\n </div>\n\n {/* Shimmer effect underneath */}\n <div\n className={cn(\n \"shimmer-effect absolute inset-0 bg-gradient-to-r from-muted via-muted/50 to-muted\",\n isLoading ? \"opacity-100\" : \"opacity-0\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background:\n \"linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)\",\n backgroundSize: \"200% 100%\",\n animation: \"slop-shimmer 2s ease-in-out infinite\",\n opacity: isLoading ? 1 : 0,\n transition: \"opacity 300ms\",\n }}\n />\n </>\n )}\n\n {/* Image */}\n <img\n key={src}\n src={src}\n alt={alt}\n {...props}\n onLoad={(e) => {\n setIsLoading(false);\n onLoad?.(e);\n }}\n onError={(e) => {\n setIsLoading(false);\n onError?.(e);\n }}\n className={cn(\n \"h-full w-full transition-opacity duration-500\",\n isLoading ? \"opacity-0\" : \"opacity-100\",\n imageClassName,\n )}\n style={{\n height: \"100%\",\n width: \"100%\",\n objectFit,\n transition: \"opacity 500ms\",\n opacity: isLoading ? 0 : 1,\n }}\n />\n </div>\n </div>\n </>\n );\n};\n","import React, { useMemo, useState, useRef, useEffect } from \"react\";\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nimport {\n buildVideoUrl,\n type SlopVideoOptions,\n type VideoAspectRatio,\n} from \"@slopmachine/core\";\n\n// Utility for Tailwind classes\nfunction cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport interface SlopVideoProps\n extends\n Omit<React.VideoHTMLAttributes<HTMLVideoElement>, \"src\">,\n Omit<SlopVideoOptions, \"aspectRatio\"> {\n /**\n * The aspect ratio of the generated video. Defaults to \"16:9\".\n */\n aspectRatio?: VideoAspectRatio;\n /**\n * Custom React node to display while the video is loading.\n * If not provided, a default spinner and shimmer effect will be shown.\n */\n loader?: React.ReactNode;\n}\n\n/**\n * Renders a video generated by Slop Machine.\n *\n * This component automatically constructs the correct URL for the Slop Machine API,\n * displays a loading skeleton or custom loader while fetching, and handles errors.\n *\n * @example\n * ```tsx\n * // Using a Bucket\n * <SlopVideo\n * bucketId=\"my-bucket\"\n * resultId=\"some-result\"\n * aspectRatio=\"16:9\"\n * className=\"rounded-lg\"\n * autoPlay\n * loop\n * muted\n * />\n *\n * // Using a Pipeline with runtime prompt\n * <SlopVideo\n * pipelineId=\"pipe_live_abc123\"\n * prompt=\"Drone shot of a cyberpunk city in rain\"\n * aspectRatio=\"16:9\"\n * metadata={{ campaign: \"spring_launch\" }}\n * />\n * ```\n *\n * @version 0.5.0\n */\nexport const SlopVideo: React.FC<SlopVideoProps> = ({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n className,\n aspectRatio = \"16:9\",\n version,\n resultId,\n variables = {},\n duration,\n baseUrl,\n original,\n attachments,\n loader,\n autoPlay = true,\n loop = true,\n muted = true,\n playsInline = true,\n onLoadedData,\n onError,\n ...props\n}) => {\n // Construct API URL\n const rawSrc = useMemo(\n () =>\n buildVideoUrl({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n aspectRatio,\n version,\n resultId,\n variables,\n duration,\n baseUrl,\n original,\n attachments,\n }),\n [\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n aspectRatio,\n version,\n resultId,\n variables,\n duration,\n baseUrl,\n original,\n attachments,\n ],\n );\n\n const [src, setSrc] = useState(rawSrc);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n setSrc(rawSrc);\n }, 100);\n return () => clearTimeout(timer);\n }, [rawSrc]);\n\n const [isLoading, setIsLoading] = useState(true);\n const [currentSrc, setCurrentSrc] = useState(src);\n const videoRef = useRef<HTMLVideoElement>(null);\n\n if (src !== currentSrc) {\n setCurrentSrc(src);\n setIsLoading(true);\n }\n\n useEffect(() => {\n if (!src) return;\n\n fetch(src, { method: \"HEAD\" })\n .then(async (res) => {\n if (!res.ok) {\n let errorMessage = res.statusText;\n try {\n const errRes = await fetch(src);\n const errorText = await errRes.text();\n const errorData = JSON.parse(errorText);\n if (errorData.error) {\n errorMessage = errorData.error;\n }\n } catch (e) {\n // Failed to fetch or parse error details\n }\n console.error(\"SlopVideo error:\", res.status, errorMessage);\n setIsLoading(false);\n }\n })\n .catch((err) => {\n console.error(`Error fetching video from ${src}:`, err);\n setIsLoading(false);\n });\n }, [src]);\n\n return (\n <>\n <style>{`\n @keyframes slop-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n }\n @keyframes slop-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n }\n `}</style>\n <div className={className} style={props.style}>\n <div\n className=\"slop-wrapper relative overflow-hidden w-full h-full\"\n style={{\n aspectRatio: String(aspectRatio).replace(\":\", \"/\"),\n position: \"relative\",\n overflow: \"hidden\",\n width: \"100%\",\n height: \"100%\",\n }}\n >\n {/* Loading UI */}\n {loader ? (\n <div\n className={cn(\n \"absolute inset-0 z-10 flex items-center justify-center transition-opacity duration-300\",\n isLoading ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n opacity: isLoading ? 1 : 0,\n pointerEvents: isLoading ? \"auto\" : \"none\",\n transition: \"opacity 300ms ease-in-out\",\n }}\n >\n {loader}\n </div>\n ) : (\n <>\n {/* Loading overlay */}\n <div\n className={cn(\n \"absolute inset-0 z-10 flex items-center justify-center bg-muted transition-opacity duration-300\",\n isLoading ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"var(--muted, #f3f4f6)\",\n opacity: isLoading ? 1 : 0,\n pointerEvents: isLoading ? \"auto\" : \"none\",\n transition: \"opacity 300ms ease-in-out\",\n }}\n >\n <div\n className=\"flex flex-col items-center gap-2\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n }}\n >\n <svg\n className=\"spinner size-6 text-muted-foreground\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n style={{\n width: \"24px\",\n height: \"24px\",\n color: \"var(--muted-foreground, #6b7280)\",\n animation: \"slop-spin 1s linear infinite\",\n }}\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n style={{ opacity: 0.25 }}\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n style={{ opacity: 0.75 }}\n />\n </svg>\n <span\n className=\"text-xs text-muted-foreground\"\n style={{\n fontSize: \"0.75rem\",\n color: \"var(--muted-foreground, #6b7280)\",\n }}\n >\n Loading...\n </span>\n </div>\n </div>\n\n {/* Shimmer effect underneath */}\n <div\n className={cn(\n \"shimmer-effect absolute inset-0 bg-gradient-to-r from-muted via-muted/50 to-muted\",\n isLoading ? \"opacity-100\" : \"opacity-0\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background:\n \"linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)\",\n backgroundSize: \"200% 100%\",\n animation: \"slop-shimmer 2s ease-in-out infinite\",\n opacity: isLoading ? 1 : 0,\n transition: \"opacity 300ms\",\n }}\n />\n </>\n )}\n\n {/* Video */}\n <video\n key={src}\n ref={videoRef}\n src={src}\n autoPlay={autoPlay}\n loop={loop}\n muted={muted}\n playsInline={playsInline}\n {...props}\n onLoadedData={(e) => {\n setIsLoading(false);\n onLoadedData?.(e);\n }}\n onError={(e) => {\n setIsLoading(false);\n onError?.(e);\n }}\n className={cn(\n \"h-full w-full object-cover transition-opacity duration-500\",\n isLoading ? \"opacity-0\" : \"opacity-100\",\n )}\n style={{\n height: \"100%\",\n width: \"100%\",\n objectFit: \"cover\",\n transition: \"opacity 500ms\",\n opacity: isLoading ? 0 : 1,\n }}\n />\n </div>\n </div>\n </>\n );\n};\n","import React, { useEffect, useMemo, useState } from \"react\";\nimport { buildTextUrl, type SlopTextOptions } from \"@slopmachine/core\";\nimport { marked } from \"marked\";\n\nexport interface SlopTextProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, \"children\">,\n SlopTextOptions {\n /**\n * Optional loading component to show while the text is being generated\n */\n fallback?: React.ReactNode;\n /**\n * Optional error component to show if the text generation fails\n */\n errorFallback?: React.ReactNode;\n}\n\n/**\n * Renders text generated by Slop Machine.\n *\n * This component automatically constructs the correct URL for the Slop Machine API,\n * fetches the generated text, and displays it with optional loading and error states.\n *\n * @example\n * ```tsx\n * // Using a Bucket\n * <SlopText\n * bucketId=\"my-text-bucket\"\n * fallback={<div>Loading story...</div>}\n * errorFallback={<div>Failed to load story</div>}\n * className=\"text-lg text-gray-800\"\n * />\n *\n * // Using a Pipeline with runtime prompt\n * <SlopText\n * pipelineId=\"pipe_live_abc123\"\n * prompt=\"Write a whimsical haiku about quantum physics\"\n * metadata={{ userId: \"123\" }}\n * />\n * ```\n *\n * @version 0.5.0\n */\nexport const SlopText = React.forwardRef<HTMLDivElement, SlopTextProps>(\n (\n {\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n version,\n resultId,\n variables,\n baseUrl,\n attachments,\n fallback,\n errorFallback,\n ...props\n },\n ref,\n ) => {\n const [text, setText] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const rawUrl = useMemo(\n () =>\n buildTextUrl({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n version,\n resultId,\n variables,\n baseUrl,\n attachments,\n }),\n [\n bucketId,\n pipelineId,\n siloId,\n prompt,\n JSON.stringify(metadata),\n version,\n resultId,\n JSON.stringify(variables),\n baseUrl,\n JSON.stringify(attachments),\n ],\n );\n\n useEffect(() => {\n let isMounted = true;\n\n const fetchText = async () => {\n try {\n setLoading(true);\n setError(null);\n\n // Fetch the URL to get the content, following redirects automatically\n const response = await fetch(rawUrl, { cache: \"no-store\" });\n if (!response.ok) {\n let errorMessage = response.statusText;\n try {\n const errorText = await response.text();\n const errorData = JSON.parse(errorText);\n if (errorData.error) {\n errorMessage = errorData.error;\n }\n } catch (e) {\n // Ignore JSON parse errors if the response isn't JSON\n }\n throw new Error(`Failed to fetch text: ${errorMessage}`);\n }\n const content = await response.text();\n if (isMounted) {\n setText(content);\n }\n } catch (err) {\n if (isMounted) {\n const finalError =\n err instanceof Error ? err : new Error(\"Failed to generate text\");\n console.error(finalError);\n setError(finalError);\n }\n } finally {\n if (isMounted) {\n setLoading(false);\n }\n }\n };\n\n fetchText();\n\n return () => {\n isMounted = false;\n };\n }, [rawUrl]);\n\n if (error && errorFallback) {\n return <>{errorFallback}</>;\n }\n\n if (loading) {\n if (fallback) {\n return <>{fallback}</>;\n }\n return (\n <div className={props.className} style={props.style}>\n <style>{`\n @keyframes slop-shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n @keyframes slop-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `}</style>\n <div\n className=\"slop-wrapper relative overflow-hidden w-full\"\n style={{\n position: \"relative\",\n overflow: \"hidden\",\n width: \"100%\",\n minHeight: \"100px\",\n }}\n >\n {/* Loading overlay */}\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center bg-muted transition-opacity duration-300\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"var(--muted, #f3f4f6)\",\n }}\n >\n <div\n className=\"flex flex-col items-center gap-2\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n }}\n >\n <svg\n className=\"spinner size-6 text-muted-foreground\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n style={{\n width: \"24px\",\n height: \"24px\",\n color: \"var(--muted-foreground, #6b7280)\",\n animation: \"slop-spin 1s linear infinite\",\n }}\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n style={{ opacity: 0.25 }}\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n style={{ opacity: 0.75 }}\n />\n </svg>\n <span\n className=\"text-xs text-muted-foreground\"\n style={{\n fontSize: \"0.75rem\",\n color: \"var(--muted-foreground, #6b7280)\",\n }}\n >\n Loading...\n </span>\n </div>\n </div>\n\n {/* Shimmer effect underneath */}\n <div\n className=\"shimmer-effect absolute inset-0 bg-gradient-to-r from-muted via-muted/50 to-muted\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background:\n \"linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)\",\n backgroundSize: \"200% 100%\",\n animation: \"slop-shimmer 2s ease-in-out infinite\",\n }}\n />\n </div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n {...props}\n dangerouslySetInnerHTML={{\n __html: text ? (marked.parse(text) as string) : \"\",\n }}\n />\n );\n },\n);\n\nSlopText.displayName = \"SlopText\";\n","export { SlopImage, type SlopImageProps } from \"./components/SlopImage\";\nexport { SlopVideo, type SlopVideoProps } from \"./components/SlopVideo\";\nexport { SlopText, type SlopTextProps } from \"./components/SlopText\";\n\nexport type {\n ImageAspectRatio,\n VideoAspectRatio,\n SlopImageOptions,\n SlopVideoOptions,\n ResultRating,\n RateResultOptions,\n RateResultResponse,\n} from \"@slopmachine/core\";\n\nexport { preloadImage, rateResult } from \"@slopmachine/core\";\n"],"mappings":"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","names":["useMemo","useState","clsx","twMerge","Fragment","jsx","jsxs","cn","React","useEffect","useMemo","useState","Fragment","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/components/SlopImage.tsx","../src/components/ErrorOverlay.tsx","../src/components/SlopVideo.tsx","../src/components/SlopText.tsx","../src/index.ts"],"sourcesContent":["import React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nimport {\n buildImageUrl,\n createRenderUrlMonitor,\n type SlopMachineError,\n type SlopImageOptions,\n type ImageAspectRatio,\n} from \"@slopmachine/core\";\nimport { ErrorOverlay, type SlopErrorFallback } from \"./ErrorOverlay\";\n\n// Utility for Tailwind classes\nfunction cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport interface SlopImageProps\n extends\n Omit<React.ImgHTMLAttributes<HTMLImageElement>, \"src\" | \"alt\">,\n Omit<SlopImageOptions, \"aspectRatio\"> {\n /**\n * The aspect ratio of the generated image. Defaults to \"1:1\".\n */\n aspectRatio?: ImageAspectRatio;\n /**\n * Custom React node to display while the image is loading.\n * If not provided, a default spinner and shimmer effect will be shown.\n */\n loader?: React.ReactNode;\n /**\n * Content to display if the image fails to generate or load.\n * Pass a node, or a function that receives the `SlopMachineError` and returns a node.\n * If not provided, a default error message is shown in place of the image.\n */\n errorFallback?: SlopErrorFallback;\n /**\n * Called once per URL when the image fails to generate or load.\n * `error.status` holds the HTTP status when the API reported the failure.\n */\n onGenerationError?: (error: SlopMachineError) => void;\n /**\n * How the image should be resized to fit its container. Defaults to \"cover\".\n */\n objectFit?: React.CSSProperties[\"objectFit\"];\n /**\n * Additional CSS classes to apply directly to the `<img />` element.\n */\n imageClassName?: string;\n}\n\n/**\n * Renders an image generated by Slop Machine.\n *\n * This component automatically constructs the correct URL for the Slop Machine API,\n * displays a loading skeleton or custom loader while fetching, and handles errors.\n *\n * @example\n * ```tsx\n * // Using a Bucket\n * <SlopImage\n * bucketId=\"my-bucket\"\n * resultId=\"some-result\"\n * aspectRatio=\"16:9\"\n * className=\"rounded-lg\"\n * objectFit=\"cover\"\n * />\n *\n * // Using a Pipeline with runtime prompt\n * <SlopImage\n * pipelineId=\"pipe_live_abc123\"\n * prompt=\"1960s retro robot eating pizza on Mars\"\n * aspectRatio=\"16:9\"\n * metadata={{ userId: \"123\" }}\n * />\n * ```\n *\n * @version 0.7.0\n */\nexport const SlopImage: React.FC<SlopImageProps> = ({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n className,\n aspectRatio = \"1:1\",\n model,\n version,\n resultId,\n variables = {},\n baseUrl,\n original,\n attachments,\n loader,\n errorFallback,\n onGenerationError,\n objectFit = \"cover\",\n imageClassName,\n style,\n onLoad,\n onError,\n ...props\n}) => {\n // Construct API URL\n const rawSrc = useMemo(\n () =>\n buildImageUrl({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n aspectRatio,\n model,\n version,\n resultId,\n variables,\n baseUrl,\n original,\n attachments,\n }),\n [\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n aspectRatio,\n model,\n version,\n resultId,\n variables,\n baseUrl,\n original,\n attachments,\n ],\n );\n\n const [src, setSrc] = useState(rawSrc);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n setSrc(rawSrc);\n }, 100);\n return () => clearTimeout(timer);\n }, [rawSrc]);\n\n const alt = \"Image produced by Slop Machine (slopmachine.dev)\";\n\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<SlopMachineError | null>(null);\n const [currentSrc, setCurrentSrc] = useState(src);\n if (src !== currentSrc) {\n setCurrentSrc(src);\n setIsLoading(true);\n setError(null);\n }\n\n // Keep the latest callback without re-running effects when it changes identity\n const onGenerationErrorRef = useRef(onGenerationError);\n useEffect(() => {\n onGenerationErrorRef.current = onGenerationError;\n });\n\n // Shares one URL check between the HEAD probe and the media element's error event\n const monitor = useMemo(\n () =>\n createRenderUrlMonitor((err) => {\n console.error(\"SlopImage error:\", err.status ?? \"\", err.message);\n setIsLoading(false);\n setError(err);\n onGenerationErrorRef.current?.(err);\n }),\n [],\n );\n\n useEffect(() => {\n if (!src) return;\n monitor.watch(src);\n return () => monitor.stop();\n }, [src, monitor]);\n\n return (\n <>\n <style>{`\n @keyframes slop-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n }\n @keyframes slop-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n }\n `}</style>\n <div className={className} style={style}>\n <div\n className=\"slop-wrapper relative overflow-hidden w-full h-full\"\n style={{\n aspectRatio: String(aspectRatio).replace(\":\", \"/\"),\n position: \"relative\",\n overflow: \"hidden\",\n width: \"100%\",\n height: \"100%\",\n }}\n >\n {/* Loading UI */}\n {loader ? (\n <div\n className={cn(\n \"absolute inset-0 z-10 flex items-center justify-center transition-opacity duration-300\",\n isLoading ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n opacity: isLoading ? 1 : 0,\n pointerEvents: isLoading ? \"auto\" : \"none\",\n transition: \"opacity 300ms ease-in-out\",\n }}\n >\n {loader}\n </div>\n ) : (\n <>\n {/* Loading overlay */}\n <div\n className={cn(\n \"absolute inset-0 z-10 flex items-center justify-center bg-muted transition-opacity duration-300\",\n isLoading ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"var(--muted, #f3f4f6)\",\n opacity: isLoading ? 1 : 0,\n pointerEvents: isLoading ? \"auto\" : \"none\",\n transition: \"opacity 300ms ease-in-out\",\n }}\n >\n <div\n className=\"flex flex-col items-center gap-2\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n }}\n >\n <svg\n className=\"spinner size-6 text-muted-foreground\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n style={{\n width: \"24px\",\n height: \"24px\",\n color: \"var(--muted-foreground, #6b7280)\",\n animation: \"slop-spin 1s linear infinite\",\n }}\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n style={{ opacity: 0.25 }}\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n style={{ opacity: 0.75 }}\n />\n </svg>\n <span\n className=\"text-xs text-muted-foreground\"\n style={{\n fontSize: \"0.75rem\",\n color: \"var(--muted-foreground, #6b7280)\",\n }}\n >\n Loading...\n </span>\n </div>\n </div>\n\n {/* Shimmer effect underneath */}\n <div\n className={cn(\n \"shimmer-effect absolute inset-0 bg-gradient-to-r from-muted via-muted/50 to-muted\",\n isLoading ? \"opacity-100\" : \"opacity-0\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background:\n \"linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)\",\n backgroundSize: \"200% 100%\",\n animation: \"slop-shimmer 2s ease-in-out infinite\",\n opacity: isLoading ? 1 : 0,\n transition: \"opacity 300ms\",\n }}\n />\n </>\n )}\n\n {error && (\n <ErrorOverlay\n error={error}\n errorFallback={errorFallback}\n label=\"Failed to load image\"\n />\n )}\n\n {/* Image */}\n <img\n key={src}\n src={src}\n alt={alt}\n {...props}\n onLoad={(e) => {\n setIsLoading(false);\n onLoad?.(e);\n }}\n onError={(e) => {\n monitor.mediaFailed(src, \"Failed to load image\");\n onError?.(e);\n }}\n aria-hidden={error ? true : props[\"aria-hidden\"]}\n className={cn(\n \"h-full w-full transition-opacity duration-500\",\n isLoading || error ? \"opacity-0\" : \"opacity-100\",\n imageClassName,\n )}\n style={{\n height: \"100%\",\n width: \"100%\",\n objectFit,\n transition: \"opacity 500ms\",\n opacity: isLoading || error ? 0 : 1,\n }}\n />\n </div>\n </div>\n </>\n );\n};\n","import React from \"react\";\nimport type { SlopMachineError } from \"@slopmachine/core\";\n\n/**\n * Content to render when generation or loading fails. Either a node, or a\n * function that receives the error and returns a node.\n */\nexport type SlopErrorFallback =\n React.ReactNode | ((error: SlopMachineError) => React.ReactNode);\n\ninterface ErrorOverlayProps {\n error: SlopMachineError;\n errorFallback?: SlopErrorFallback;\n /**\n * Message shown by the default error UI, e.g. \"Failed to load image\".\n */\n label: string;\n}\n\nconst overlayStyle: React.CSSProperties = {\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n};\n\n/**\n * Internal overlay shown over the media slot when generation or loading fails.\n * Renders the consumer's `errorFallback` if provided, otherwise a default message.\n */\nexport const ErrorOverlay: React.FC<ErrorOverlayProps> = ({\n error,\n errorFallback,\n label,\n}) => {\n if (errorFallback !== undefined) {\n return (\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center\"\n style={overlayStyle}\n >\n {typeof errorFallback === \"function\"\n ? errorFallback(error)\n : errorFallback}\n </div>\n );\n }\n\n return (\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center bg-muted\"\n style={{ ...overlayStyle, backgroundColor: \"var(--muted, #f3f4f6)\" }}\n title={error.message}\n >\n <div\n className=\"flex flex-col items-center gap-2 text-muted-foreground\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n color: \"var(--muted-foreground, #6b7280)\",\n }}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n style={{ width: \"24px\", height: \"24px\" }}\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n <span className=\"text-xs\" style={{ fontSize: \"0.75rem\" }}>\n {label}\n </span>\n </div>\n </div>\n );\n};\n","import React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nimport {\n buildVideoUrl,\n createRenderUrlMonitor,\n type SlopMachineError,\n type SlopVideoOptions,\n type VideoAspectRatio,\n} from \"@slopmachine/core\";\nimport { ErrorOverlay, type SlopErrorFallback } from \"./ErrorOverlay\";\n\n// Utility for Tailwind classes\nfunction cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport interface SlopVideoProps\n extends\n Omit<React.VideoHTMLAttributes<HTMLVideoElement>, \"src\">,\n Omit<SlopVideoOptions, \"aspectRatio\"> {\n /**\n * The aspect ratio of the generated video. Defaults to \"16:9\".\n */\n aspectRatio?: VideoAspectRatio;\n /**\n * Custom React node to display while the video is loading.\n * If not provided, a default spinner and shimmer effect will be shown.\n */\n loader?: React.ReactNode;\n /**\n * Content to display if the video fails to generate or load.\n * Pass a node, or a function that receives the `SlopMachineError` and returns a node.\n * If not provided, a default error message is shown in place of the video.\n */\n errorFallback?: SlopErrorFallback;\n /**\n * Called once per URL when the video fails to generate or load.\n * `error.status` holds the HTTP status when the API reported the failure.\n */\n onGenerationError?: (error: SlopMachineError) => void;\n}\n\n/**\n * Renders a video generated by Slop Machine.\n *\n * This component automatically constructs the correct URL for the Slop Machine API,\n * displays a loading skeleton or custom loader while fetching, and handles errors.\n *\n * @example\n * ```tsx\n * // Using a Bucket\n * <SlopVideo\n * bucketId=\"my-bucket\"\n * resultId=\"some-result\"\n * aspectRatio=\"16:9\"\n * className=\"rounded-lg\"\n * autoPlay\n * loop\n * muted\n * />\n *\n * // Using a Pipeline with runtime prompt\n * <SlopVideo\n * pipelineId=\"pipe_live_abc123\"\n * prompt=\"Drone shot of a cyberpunk city in rain\"\n * aspectRatio=\"16:9\"\n * metadata={{ campaign: \"spring_launch\" }}\n * />\n * ```\n *\n * @version 0.7.0\n */\nexport const SlopVideo: React.FC<SlopVideoProps> = ({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n className,\n aspectRatio = \"16:9\",\n version,\n resultId,\n variables = {},\n duration,\n baseUrl,\n original,\n attachments,\n loader,\n errorFallback,\n onGenerationError,\n autoPlay = true,\n loop = true,\n muted = true,\n playsInline = true,\n onLoadedData,\n onError,\n ...props\n}) => {\n // Construct API URL\n const rawSrc = useMemo(\n () =>\n buildVideoUrl({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n aspectRatio,\n version,\n resultId,\n variables,\n duration,\n baseUrl,\n original,\n attachments,\n }),\n [\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n aspectRatio,\n version,\n resultId,\n variables,\n duration,\n baseUrl,\n original,\n attachments,\n ],\n );\n\n const [src, setSrc] = useState(rawSrc);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n setSrc(rawSrc);\n }, 100);\n return () => clearTimeout(timer);\n }, [rawSrc]);\n\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<SlopMachineError | null>(null);\n const [currentSrc, setCurrentSrc] = useState(src);\n const videoRef = useRef<HTMLVideoElement>(null);\n if (src !== currentSrc) {\n setCurrentSrc(src);\n setIsLoading(true);\n setError(null);\n }\n\n // Keep the latest callback without re-running effects when it changes identity\n const onGenerationErrorRef = useRef(onGenerationError);\n useEffect(() => {\n onGenerationErrorRef.current = onGenerationError;\n });\n\n // Shares one URL check between the HEAD probe and the media element's error event\n const monitor = useMemo(\n () =>\n createRenderUrlMonitor((err) => {\n console.error(\"SlopVideo error:\", err.status ?? \"\", err.message);\n setIsLoading(false);\n setError(err);\n onGenerationErrorRef.current?.(err);\n }),\n [],\n );\n\n useEffect(() => {\n if (!src) return;\n monitor.watch(src);\n return () => monitor.stop();\n }, [src, monitor]);\n\n return (\n <>\n <style>{`\n @keyframes slop-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n }\n @keyframes slop-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n }\n `}</style>\n <div className={className} style={props.style}>\n <div\n className=\"slop-wrapper relative overflow-hidden w-full h-full\"\n style={{\n aspectRatio: String(aspectRatio).replace(\":\", \"/\"),\n position: \"relative\",\n overflow: \"hidden\",\n width: \"100%\",\n height: \"100%\",\n }}\n >\n {/* Loading UI */}\n {loader ? (\n <div\n className={cn(\n \"absolute inset-0 z-10 flex items-center justify-center transition-opacity duration-300\",\n isLoading ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n opacity: isLoading ? 1 : 0,\n pointerEvents: isLoading ? \"auto\" : \"none\",\n transition: \"opacity 300ms ease-in-out\",\n }}\n >\n {loader}\n </div>\n ) : (\n <>\n {/* Loading overlay */}\n <div\n className={cn(\n \"absolute inset-0 z-10 flex items-center justify-center bg-muted transition-opacity duration-300\",\n isLoading ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"var(--muted, #f3f4f6)\",\n opacity: isLoading ? 1 : 0,\n pointerEvents: isLoading ? \"auto\" : \"none\",\n transition: \"opacity 300ms ease-in-out\",\n }}\n >\n <div\n className=\"flex flex-col items-center gap-2\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n }}\n >\n <svg\n className=\"spinner size-6 text-muted-foreground\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n style={{\n width: \"24px\",\n height: \"24px\",\n color: \"var(--muted-foreground, #6b7280)\",\n animation: \"slop-spin 1s linear infinite\",\n }}\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n style={{ opacity: 0.25 }}\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n style={{ opacity: 0.75 }}\n />\n </svg>\n <span\n className=\"text-xs text-muted-foreground\"\n style={{\n fontSize: \"0.75rem\",\n color: \"var(--muted-foreground, #6b7280)\",\n }}\n >\n Loading...\n </span>\n </div>\n </div>\n\n {/* Shimmer effect underneath */}\n <div\n className={cn(\n \"shimmer-effect absolute inset-0 bg-gradient-to-r from-muted via-muted/50 to-muted\",\n isLoading ? \"opacity-100\" : \"opacity-0\",\n )}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background:\n \"linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)\",\n backgroundSize: \"200% 100%\",\n animation: \"slop-shimmer 2s ease-in-out infinite\",\n opacity: isLoading ? 1 : 0,\n transition: \"opacity 300ms\",\n }}\n />\n </>\n )}\n\n {error && (\n <ErrorOverlay\n error={error}\n errorFallback={errorFallback}\n label=\"Failed to load video\"\n />\n )}\n\n {/* Video */}\n <video\n key={src}\n ref={videoRef}\n src={src}\n autoPlay={autoPlay}\n loop={loop}\n muted={muted}\n playsInline={playsInline}\n {...props}\n onLoadedData={(e) => {\n setIsLoading(false);\n onLoadedData?.(e);\n }}\n onError={(e) => {\n monitor.mediaFailed(src, \"Failed to load video\");\n onError?.(e);\n }}\n aria-hidden={error ? true : props[\"aria-hidden\"]}\n className={cn(\n \"h-full w-full object-cover transition-opacity duration-500\",\n isLoading || error ? \"opacity-0\" : \"opacity-100\",\n )}\n style={{\n height: \"100%\",\n width: \"100%\",\n objectFit: \"cover\",\n transition: \"opacity 500ms\",\n opacity: isLoading || error ? 0 : 1,\n }}\n />\n </div>\n </div>\n </>\n );\n};\n","import React, { useEffect, useMemo, useState } from \"react\";\nimport {\n buildTextUrl,\n renderMarkdown,\n type SlopTextOptions,\n} from \"@slopmachine/core\";\n\nexport interface SlopTextProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, \"children\">,\n SlopTextOptions {\n /**\n * Optional loading component to show while the text is being generated\n */\n fallback?: React.ReactNode;\n /**\n * Optional error component to show if the text generation fails\n */\n errorFallback?: React.ReactNode;\n}\n\n/**\n * Renders text generated by Slop Machine.\n *\n * This component automatically constructs the correct URL for the Slop Machine API,\n * fetches the generated text, and displays it with optional loading and error states.\n *\n * @example\n * ```tsx\n * // Using a Bucket\n * <SlopText\n * bucketId=\"my-text-bucket\"\n * fallback={<div>Loading story...</div>}\n * errorFallback={<div>Failed to load story</div>}\n * className=\"text-lg text-gray-800\"\n * />\n *\n * // Using a Pipeline with runtime prompt\n * <SlopText\n * pipelineId=\"pipe_live_abc123\"\n * prompt=\"Write a whimsical haiku about quantum physics\"\n * metadata={{ userId: \"123\" }}\n * />\n * ```\n *\n * @version 0.7.0\n */\nexport const SlopText = React.forwardRef<HTMLDivElement, SlopTextProps>(\n (\n {\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n version,\n resultId,\n model,\n variables,\n baseUrl,\n attachments,\n fallback,\n errorFallback,\n ...props\n },\n ref,\n ) => {\n const [text, setText] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const rawUrl = useMemo(\n () =>\n buildTextUrl({\n bucketId,\n pipelineId,\n siloId,\n prompt,\n metadata,\n version,\n resultId,\n model,\n variables,\n baseUrl,\n attachments,\n }),\n [\n bucketId,\n pipelineId,\n siloId,\n prompt,\n JSON.stringify(metadata),\n version,\n resultId,\n model,\n JSON.stringify(variables),\n baseUrl,\n JSON.stringify(attachments),\n ],\n );\n\n useEffect(() => {\n let isMounted = true;\n\n const fetchText = async () => {\n try {\n setLoading(true);\n setError(null);\n\n // Fetch the URL to get the content, following redirects automatically\n const response = await fetch(rawUrl, { cache: \"no-store\" });\n if (!response.ok) {\n let errorMessage = response.statusText;\n try {\n const errorText = await response.text();\n const errorData = JSON.parse(errorText);\n if (errorData.error) {\n errorMessage = errorData.error;\n }\n } catch (e) {\n // Ignore JSON parse errors if the response isn't JSON\n }\n throw new Error(`Failed to fetch text: ${errorMessage}`);\n }\n const content = await response.text();\n if (isMounted) {\n setText(content);\n }\n } catch (err) {\n if (isMounted) {\n const finalError =\n err instanceof Error ? err : new Error(\"Failed to generate text\");\n console.error(finalError);\n setError(finalError);\n }\n } finally {\n if (isMounted) {\n setLoading(false);\n }\n }\n };\n\n fetchText();\n\n return () => {\n isMounted = false;\n };\n }, [rawUrl]);\n\n if (error && errorFallback) {\n return <>{errorFallback}</>;\n }\n\n if (loading) {\n if (fallback) {\n return <>{fallback}</>;\n }\n return (\n <div className={props.className} style={props.style}>\n <style>{`\n @keyframes slop-shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n @keyframes slop-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `}</style>\n <div\n className=\"slop-wrapper relative overflow-hidden w-full\"\n style={{\n position: \"relative\",\n overflow: \"hidden\",\n width: \"100%\",\n minHeight: \"100px\",\n }}\n >\n {/* Loading overlay */}\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center bg-muted transition-opacity duration-300\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"var(--muted, #f3f4f6)\",\n }}\n >\n <div\n className=\"flex flex-col items-center gap-2\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n }}\n >\n <svg\n className=\"spinner size-6 text-muted-foreground\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n style={{\n width: \"24px\",\n height: \"24px\",\n color: \"var(--muted-foreground, #6b7280)\",\n animation: \"slop-spin 1s linear infinite\",\n }}\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n style={{ opacity: 0.25 }}\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n style={{ opacity: 0.75 }}\n />\n </svg>\n <span\n className=\"text-xs text-muted-foreground\"\n style={{\n fontSize: \"0.75rem\",\n color: \"var(--muted-foreground, #6b7280)\",\n }}\n >\n Loading...\n </span>\n </div>\n </div>\n\n {/* Shimmer effect underneath */}\n <div\n className=\"shimmer-effect absolute inset-0 bg-gradient-to-r from-muted via-muted/50 to-muted\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n background:\n \"linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)\",\n backgroundSize: \"200% 100%\",\n animation: \"slop-shimmer 2s ease-in-out infinite\",\n }}\n />\n </div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n {...props}\n dangerouslySetInnerHTML={{\n __html: text ? renderMarkdown(text) : \"\",\n }}\n />\n );\n },\n);\n\nSlopText.displayName = \"SlopText\";\n","export { SlopImage, type SlopImageProps } from \"./components/SlopImage\";\nexport { SlopVideo, type SlopVideoProps } from \"./components/SlopVideo\";\nexport { SlopText, type SlopTextProps } from \"./components/SlopText\";\nexport type { SlopErrorFallback } from \"./components/ErrorOverlay\";\n\nexport type {\n ImageAspectRatio,\n VideoAspectRatio,\n SlopImageOptions,\n SlopVideoOptions,\n SlopTextOptions,\n ResultRating,\n RateResultOptions,\n RateResultResponse,\n} from \"@slopmachine/core\";\n\nexport {\n preloadImage,\n preloadVideo,\n preloadText,\n rateResult,\n SlopMachineError,\n} from \"@slopmachine/core\";\n"],"mappings":"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","names":["jsx","jsxs","useEffect","useMemo","useRef","useState","clsx","twMerge","createRenderUrlMonitor","Fragment","jsx","jsxs","cn","twMerge","clsx","useMemo","useState","useEffect","useRef","createRenderUrlMonitor","React","useEffect","useMemo","useState","Fragment","jsx","jsxs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slopmachine/react",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "llms": "https://docs.slopmachine.dev/llms.txt",
5
5
  "llmsFull": "https://docs.slopmachine.dev/llms-full.txt",
6
6
  "description": "The official React SDK for Slop Machine",
@@ -44,19 +44,11 @@
44
44
  "vite": "^7.3.1"
45
45
  },
46
46
  "dependencies": {
47
- "@radix-ui/react-label": "^2.1.8",
48
- "@radix-ui/react-select": "^2.2.6",
49
- "@radix-ui/react-slot": "^1.2.4",
50
- "@slopmachine/core": "^0.5.0",
51
- "@tailwindcss/postcss": "^4.1.18",
52
- "@tailwindcss/vite": "^4.1.18",
53
- "@types/marked": "^5.0.2",
54
- "class-variance-authority": "^0.7.1",
47
+ "@slopmachine/core": "^0.7.0",
55
48
  "clsx": "^2.1.1",
56
- "lucide-react": "^0.563.0",
57
- "marked": "^17.0.4",
58
- "shiki": "^3.21.0",
59
- "tailwind-merge": "^3.4.0",
60
- "tailwindcss-animate": "^1.0.7"
49
+ "tailwind-merge": "^3.4.0"
50
+ },
51
+ "peerDependencies": {
52
+ "react": ">=18"
61
53
  }
62
54
  }
@@ -0,0 +1,91 @@
1
+ import React from "react";
2
+ import type { SlopMachineError } from "@slopmachine/core";
3
+
4
+ /**
5
+ * Content to render when generation or loading fails. Either a node, or a
6
+ * function that receives the error and returns a node.
7
+ */
8
+ export type SlopErrorFallback =
9
+ React.ReactNode | ((error: SlopMachineError) => React.ReactNode);
10
+
11
+ interface ErrorOverlayProps {
12
+ error: SlopMachineError;
13
+ errorFallback?: SlopErrorFallback;
14
+ /**
15
+ * Message shown by the default error UI, e.g. "Failed to load image".
16
+ */
17
+ label: string;
18
+ }
19
+
20
+ const overlayStyle: React.CSSProperties = {
21
+ position: "absolute",
22
+ top: 0,
23
+ left: 0,
24
+ right: 0,
25
+ bottom: 0,
26
+ zIndex: 10,
27
+ display: "flex",
28
+ alignItems: "center",
29
+ justifyContent: "center",
30
+ };
31
+
32
+ /**
33
+ * Internal overlay shown over the media slot when generation or loading fails.
34
+ * Renders the consumer's `errorFallback` if provided, otherwise a default message.
35
+ */
36
+ export const ErrorOverlay: React.FC<ErrorOverlayProps> = ({
37
+ error,
38
+ errorFallback,
39
+ label,
40
+ }) => {
41
+ if (errorFallback !== undefined) {
42
+ return (
43
+ <div
44
+ className="absolute inset-0 z-10 flex items-center justify-center"
45
+ style={overlayStyle}
46
+ >
47
+ {typeof errorFallback === "function"
48
+ ? errorFallback(error)
49
+ : errorFallback}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ return (
55
+ <div
56
+ className="absolute inset-0 z-10 flex items-center justify-center bg-muted"
57
+ style={{ ...overlayStyle, backgroundColor: "var(--muted, #f3f4f6)" }}
58
+ title={error.message}
59
+ >
60
+ <div
61
+ className="flex flex-col items-center gap-2 text-muted-foreground"
62
+ style={{
63
+ display: "flex",
64
+ flexDirection: "column",
65
+ alignItems: "center",
66
+ gap: "0.5rem",
67
+ color: "var(--muted-foreground, #6b7280)",
68
+ }}
69
+ >
70
+ <svg
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ viewBox="0 0 24 24"
73
+ fill="none"
74
+ stroke="currentColor"
75
+ strokeWidth="2"
76
+ strokeLinecap="round"
77
+ strokeLinejoin="round"
78
+ aria-hidden="true"
79
+ style={{ width: "24px", height: "24px" }}
80
+ >
81
+ <circle cx="12" cy="12" r="10" />
82
+ <line x1="12" y1="8" x2="12" y2="12" />
83
+ <line x1="12" y1="16" x2="12.01" y2="16" />
84
+ </svg>
85
+ <span className="text-xs" style={{ fontSize: "0.75rem" }}>
86
+ {label}
87
+ </span>
88
+ </div>
89
+ </div>
90
+ );
91
+ };
@@ -1,12 +1,15 @@
1
- import React, { useMemo, useState } from "react";
1
+ import React, { useEffect, useMemo, useRef, useState } from "react";
2
2
  import { clsx, type ClassValue } from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
4
 
5
5
  import {
6
6
  buildImageUrl,
7
+ createRenderUrlMonitor,
8
+ type SlopMachineError,
7
9
  type SlopImageOptions,
8
10
  type ImageAspectRatio,
9
11
  } from "@slopmachine/core";
12
+ import { ErrorOverlay, type SlopErrorFallback } from "./ErrorOverlay";
10
13
 
11
14
  // Utility for Tailwind classes
12
15
  function cn(...inputs: ClassValue[]) {
@@ -26,6 +29,17 @@ export interface SlopImageProps
26
29
  * If not provided, a default spinner and shimmer effect will be shown.
27
30
  */
28
31
  loader?: React.ReactNode;
32
+ /**
33
+ * Content to display if the image fails to generate or load.
34
+ * Pass a node, or a function that receives the `SlopMachineError` and returns a node.
35
+ * If not provided, a default error message is shown in place of the image.
36
+ */
37
+ errorFallback?: SlopErrorFallback;
38
+ /**
39
+ * Called once per URL when the image fails to generate or load.
40
+ * `error.status` holds the HTTP status when the API reported the failure.
41
+ */
42
+ onGenerationError?: (error: SlopMachineError) => void;
29
43
  /**
30
44
  * How the image should be resized to fit its container. Defaults to "cover".
31
45
  */
@@ -62,7 +76,7 @@ export interface SlopImageProps
62
76
  * />
63
77
  * ```
64
78
  *
65
- * @version 0.5.0
79
+ * @version 0.7.0
66
80
  */
67
81
  export const SlopImage: React.FC<SlopImageProps> = ({
68
82
  bucketId,
@@ -72,6 +86,7 @@ export const SlopImage: React.FC<SlopImageProps> = ({
72
86
  metadata,
73
87
  className,
74
88
  aspectRatio = "1:1",
89
+ model,
75
90
  version,
76
91
  resultId,
77
92
  variables = {},
@@ -79,6 +94,8 @@ export const SlopImage: React.FC<SlopImageProps> = ({
79
94
  original,
80
95
  attachments,
81
96
  loader,
97
+ errorFallback,
98
+ onGenerationError,
82
99
  objectFit = "cover",
83
100
  imageClassName,
84
101
  style,
@@ -96,6 +113,7 @@ export const SlopImage: React.FC<SlopImageProps> = ({
96
113
  prompt,
97
114
  metadata,
98
115
  aspectRatio,
116
+ model,
99
117
  version,
100
118
  resultId,
101
119
  variables,
@@ -110,6 +128,7 @@ export const SlopImage: React.FC<SlopImageProps> = ({
110
128
  prompt,
111
129
  metadata,
112
130
  aspectRatio,
131
+ model,
113
132
  version,
114
133
  resultId,
115
134
  variables,
@@ -121,7 +140,7 @@ export const SlopImage: React.FC<SlopImageProps> = ({
121
140
 
122
141
  const [src, setSrc] = useState(rawSrc);
123
142
 
124
- React.useEffect(() => {
143
+ useEffect(() => {
125
144
  const timer = setTimeout(() => {
126
145
  setSrc(rawSrc);
127
146
  }, 100);
@@ -131,39 +150,37 @@ export const SlopImage: React.FC<SlopImageProps> = ({
131
150
  const alt = "Image produced by Slop Machine (slopmachine.dev)";
132
151
 
133
152
  const [isLoading, setIsLoading] = useState(true);
153
+ const [error, setError] = useState<SlopMachineError | null>(null);
134
154
  const [currentSrc, setCurrentSrc] = useState(src);
135
-
136
155
  if (src !== currentSrc) {
137
156
  setCurrentSrc(src);
138
157
  setIsLoading(true);
158
+ setError(null);
139
159
  }
140
160
 
141
- React.useEffect(() => {
142
- if (!src) return;
161
+ // Keep the latest callback without re-running effects when it changes identity
162
+ const onGenerationErrorRef = useRef(onGenerationError);
163
+ useEffect(() => {
164
+ onGenerationErrorRef.current = onGenerationError;
165
+ });
143
166
 
144
- fetch(src, { method: "HEAD" })
145
- .then(async (res) => {
146
- if (!res.ok) {
147
- let errorMessage = res.statusText;
148
- try {
149
- const errRes = await fetch(src);
150
- const errorText = await errRes.text();
151
- const errorData = JSON.parse(errorText);
152
- if (errorData.error) {
153
- errorMessage = errorData.error;
154
- }
155
- } catch (e) {
156
- // Failed to fetch or parse error details
157
- }
158
- console.error("SlopImage error:", res.status, errorMessage);
159
- setIsLoading(false);
160
- }
161
- })
162
- .catch((err) => {
163
- console.error(`Error fetching image from ${src}:`, err);
167
+ // Shares one URL check between the HEAD probe and the media element's error event
168
+ const monitor = useMemo(
169
+ () =>
170
+ createRenderUrlMonitor((err) => {
171
+ console.error("SlopImage error:", err.status ?? "", err.message);
164
172
  setIsLoading(false);
165
- });
166
- }, [src]);
173
+ setError(err);
174
+ onGenerationErrorRef.current?.(err);
175
+ }),
176
+ [],
177
+ );
178
+
179
+ useEffect(() => {
180
+ if (!src) return;
181
+ monitor.watch(src);
182
+ return () => monitor.stop();
183
+ }, [src, monitor]);
167
184
 
168
185
  return (
169
186
  <>
@@ -316,6 +333,14 @@ export const SlopImage: React.FC<SlopImageProps> = ({
316
333
  </>
317
334
  )}
318
335
 
336
+ {error && (
337
+ <ErrorOverlay
338
+ error={error}
339
+ errorFallback={errorFallback}
340
+ label="Failed to load image"
341
+ />
342
+ )}
343
+
319
344
  {/* Image */}
320
345
  <img
321
346
  key={src}
@@ -327,12 +352,13 @@ export const SlopImage: React.FC<SlopImageProps> = ({
327
352
  onLoad?.(e);
328
353
  }}
329
354
  onError={(e) => {
330
- setIsLoading(false);
355
+ monitor.mediaFailed(src, "Failed to load image");
331
356
  onError?.(e);
332
357
  }}
358
+ aria-hidden={error ? true : props["aria-hidden"]}
333
359
  className={cn(
334
360
  "h-full w-full transition-opacity duration-500",
335
- isLoading ? "opacity-0" : "opacity-100",
361
+ isLoading || error ? "opacity-0" : "opacity-100",
336
362
  imageClassName,
337
363
  )}
338
364
  style={{
@@ -340,7 +366,7 @@ export const SlopImage: React.FC<SlopImageProps> = ({
340
366
  width: "100%",
341
367
  objectFit,
342
368
  transition: "opacity 500ms",
343
- opacity: isLoading ? 0 : 1,
369
+ opacity: isLoading || error ? 0 : 1,
344
370
  }}
345
371
  />
346
372
  </div>
@@ -1,6 +1,9 @@
1
1
  import React, { useEffect, useMemo, useState } from "react";
2
- import { buildTextUrl, type SlopTextOptions } from "@slopmachine/core";
3
- import { marked } from "marked";
2
+ import {
3
+ buildTextUrl,
4
+ renderMarkdown,
5
+ type SlopTextOptions,
6
+ } from "@slopmachine/core";
4
7
 
5
8
  export interface SlopTextProps
6
9
  extends
@@ -40,7 +43,7 @@ export interface SlopTextProps
40
43
  * />
41
44
  * ```
42
45
  *
43
- * @version 0.5.0
46
+ * @version 0.7.0
44
47
  */
45
48
  export const SlopText = React.forwardRef<HTMLDivElement, SlopTextProps>(
46
49
  (
@@ -52,6 +55,7 @@ export const SlopText = React.forwardRef<HTMLDivElement, SlopTextProps>(
52
55
  metadata,
53
56
  version,
54
57
  resultId,
58
+ model,
55
59
  variables,
56
60
  baseUrl,
57
61
  attachments,
@@ -75,6 +79,7 @@ export const SlopText = React.forwardRef<HTMLDivElement, SlopTextProps>(
75
79
  metadata,
76
80
  version,
77
81
  resultId,
82
+ model,
78
83
  variables,
79
84
  baseUrl,
80
85
  attachments,
@@ -87,6 +92,7 @@ export const SlopText = React.forwardRef<HTMLDivElement, SlopTextProps>(
87
92
  JSON.stringify(metadata),
88
93
  version,
89
94
  resultId,
95
+ model,
90
96
  JSON.stringify(variables),
91
97
  baseUrl,
92
98
  JSON.stringify(attachments),
@@ -260,7 +266,7 @@ export const SlopText = React.forwardRef<HTMLDivElement, SlopTextProps>(
260
266
  ref={ref}
261
267
  {...props}
262
268
  dangerouslySetInnerHTML={{
263
- __html: text ? (marked.parse(text) as string) : "",
269
+ __html: text ? renderMarkdown(text) : "",
264
270
  }}
265
271
  />
266
272
  );