mirror of
https://github.com/jnsahaj/tweakcn.git
synced 2026-08-30 18:10:28 +08:00
5882f60c32
* update package locks to avoid conflict * Refactor AI Generation with AI SDK v5 (#210) * refactor(wip): Whole Chat implementation * fix: Deps * chore: Update types and create tool to handle theme generation * chore: Add hook and component to handle feedback text * chore: Check for themeStyles in asssiatant metadata * feat: Improve messages styles and feedback * chore: Update react-hook-form deps * chore: Improve Messages display * chore: Show a banner when theme is generating * chore: Optimize the Messages syncing * feat: Add debug button to MessageActions in dev mode * chore: messages * chore: Improve System prompt + tools + utils * fix: correctly display User messages with only images * feat: Handle errors in UI and adapt to AI sdk * feat: Use tool output instead of custom data parts * chore: System prompt and schema context * chore: Gemini 2.5 pro as the default model * refactor: Change name to Chat Context and apply generated theme automatically * chore: Update ai packages to latest version * feat: Stop ongoing request before starting a new chat * feat: Refactor logic for Scroll start/end sentinels * feat: Improve UI streaming with AI Elements and Stream Text utils * chore: Generate theme types and logic * feat: Allow customize the speed of streaming text * refactor: Theme generation utils * fix: Avoid duplicated Mention references * styles: Improve Chat error banner * feat: Enhance Prompt core implementation * feat: Implement enhance prompt in components * chore: Add TODOS comments * chore: Remove Openai provider * chore: Update AI adk packages * fix: Free request constant --------- Co-authored-by: Sahaj Jain <82111591+jnsahaj@users.noreply.github.com> * update lockfile * fix import * fix types * chore: Trigger theme generation before transitioning to editor page * refactor: Move AI sdk specific code into Lib and update imports * chore: Provide model ID to record usage util * refactor: Multi model provider convention * fix types * refactor: Use Zustand persist middleware versioning * feat: Improve prompts and context * feat: Optimize streaming JSONContent into text area * chore: Improve Theme Generation prompt * feat: Improve prompt * feat: Add subscription checks for enhancing prompt and Posthog tracking --------- Co-authored-by: Luis Llanes <137589205+llanesluis@users.noreply.github.com> Co-authored-by: Luis Llanes <luisllaboj@gmail.com>
448 lines
9.8 KiB
TypeScript
448 lines
9.8 KiB
TypeScript
"use client";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
export interface LoaderProps {
|
|
variant?:
|
|
| "circular"
|
|
| "classic"
|
|
| "pulse"
|
|
| "pulse-dot"
|
|
| "dots"
|
|
| "typing"
|
|
| "wave"
|
|
| "bars"
|
|
| "terminal"
|
|
| "text-blink"
|
|
| "text-shimmer"
|
|
| "loading-dots";
|
|
size?: "sm" | "md" | "lg";
|
|
text?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function CircularLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const sizeClasses = {
|
|
sm: "size-4",
|
|
md: "size-5",
|
|
lg: "size-6",
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"border-primary animate-spin rounded-full border-2 border-t-transparent",
|
|
sizeClasses[size],
|
|
className
|
|
)}
|
|
>
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function ClassicLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const sizeClasses = {
|
|
sm: "size-4",
|
|
md: "size-5",
|
|
lg: "size-6",
|
|
};
|
|
|
|
const barSizes = {
|
|
sm: { height: "6px", width: "1.5px" },
|
|
md: { height: "8px", width: "2px" },
|
|
lg: { height: "10px", width: "2.5px" },
|
|
};
|
|
|
|
return (
|
|
<div className={cn("relative", sizeClasses[size], className)}>
|
|
<div className="absolute h-full w-full">
|
|
{[...Array(12)].map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className="bg-primary absolute animate-[spinner-fade_1.2s_linear_infinite] rounded-full"
|
|
style={{
|
|
top: "0",
|
|
left: "50%",
|
|
marginLeft: size === "sm" ? "-0.75px" : size === "lg" ? "-1.25px" : "-1px",
|
|
transformOrigin: `${size === "sm" ? "0.75px" : size === "lg" ? "1.25px" : "1px"} ${size === "sm" ? "10px" : size === "lg" ? "14px" : "12px"}`,
|
|
transform: `rotate(${i * 30}deg)`,
|
|
opacity: 0,
|
|
animationDelay: `${i * 0.1}s`,
|
|
height: barSizes[size].height,
|
|
width: barSizes[size].width,
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PulseLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const sizeClasses = {
|
|
sm: "size-4",
|
|
md: "size-5",
|
|
lg: "size-6",
|
|
};
|
|
|
|
return (
|
|
<div className={cn("relative", sizeClasses[size], className)}>
|
|
<div className="border-primary absolute inset-0 animate-[thin-pulse_1.5s_ease-in-out_infinite] rounded-full border-2" />
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PulseDotLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const sizeClasses = {
|
|
sm: "size-1",
|
|
md: "size-2",
|
|
lg: "size-3",
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"bg-primary animate-[pulse-dot_1.2s_ease-in-out_infinite] rounded-full",
|
|
sizeClasses[size],
|
|
className
|
|
)}
|
|
>
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function DotsLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const dotSizes = {
|
|
sm: "h-1.5 w-1.5",
|
|
md: "h-2 w-2",
|
|
lg: "h-2.5 w-2.5",
|
|
};
|
|
|
|
const containerSizes = {
|
|
sm: "h-4",
|
|
md: "h-5",
|
|
lg: "h-6",
|
|
};
|
|
|
|
return (
|
|
<div className={cn("flex items-center space-x-1", containerSizes[size], className)}>
|
|
{[...Array(3)].map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className={cn(
|
|
"bg-primary animate-[bounce-dots_1.4s_ease-in-out_infinite] rounded-full",
|
|
dotSizes[size]
|
|
)}
|
|
style={{
|
|
animationDelay: `${i * 160}ms`,
|
|
}}
|
|
/>
|
|
))}
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function TypingLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const dotSizes = {
|
|
sm: "h-1 w-1",
|
|
md: "h-1.5 w-1.5",
|
|
lg: "h-2 w-2",
|
|
};
|
|
|
|
const containerSizes = {
|
|
sm: "h-4",
|
|
md: "h-5",
|
|
lg: "h-6",
|
|
};
|
|
|
|
return (
|
|
<div className={cn("flex items-center space-x-1", containerSizes[size], className)}>
|
|
{[...Array(3)].map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className={cn("bg-primary animate-[typing_1s_infinite] rounded-full", dotSizes[size])}
|
|
style={{
|
|
animationDelay: `${i * 250}ms`,
|
|
}}
|
|
/>
|
|
))}
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function WaveLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const barWidths = {
|
|
sm: "w-0.5",
|
|
md: "w-0.5",
|
|
lg: "w-1",
|
|
};
|
|
|
|
const containerSizes = {
|
|
sm: "h-4",
|
|
md: "h-5",
|
|
lg: "h-6",
|
|
};
|
|
|
|
const heights = {
|
|
sm: ["6px", "9px", "12px", "9px", "6px"],
|
|
md: ["8px", "12px", "16px", "12px", "8px"],
|
|
lg: ["10px", "15px", "20px", "15px", "10px"],
|
|
};
|
|
|
|
return (
|
|
<div className={cn("flex items-center gap-0.5", containerSizes[size], className)}>
|
|
{[...Array(5)].map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className={cn(
|
|
"bg-primary animate-[wave_1s_ease-in-out_infinite] rounded-full",
|
|
barWidths[size]
|
|
)}
|
|
style={{
|
|
animationDelay: `${i * 100}ms`,
|
|
height: heights[size][i],
|
|
}}
|
|
/>
|
|
))}
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function BarsLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const barWidths = {
|
|
sm: "w-1",
|
|
md: "w-1.5",
|
|
lg: "w-2",
|
|
};
|
|
|
|
const containerSizes = {
|
|
sm: "h-4 gap-1",
|
|
md: "h-5 gap-1.5",
|
|
lg: "h-6 gap-2",
|
|
};
|
|
|
|
return (
|
|
<div className={cn("flex", containerSizes[size], className)}>
|
|
{[...Array(3)].map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className={cn(
|
|
"bg-primary h-full animate-[wave-bars_1.2s_ease-in-out_infinite]",
|
|
barWidths[size]
|
|
)}
|
|
style={{
|
|
animationDelay: `${i * 0.2}s`,
|
|
}}
|
|
/>
|
|
))}
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function TerminalLoader({
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const cursorSizes = {
|
|
sm: "h-3 w-1.5",
|
|
md: "h-4 w-2",
|
|
lg: "h-5 w-2.5",
|
|
};
|
|
|
|
const textSizes = {
|
|
sm: "text-xs",
|
|
md: "text-sm",
|
|
lg: "text-base",
|
|
};
|
|
|
|
const containerSizes = {
|
|
sm: "h-4",
|
|
md: "h-5",
|
|
lg: "h-6",
|
|
};
|
|
|
|
return (
|
|
<div className={cn("flex items-center space-x-1", containerSizes[size], className)}>
|
|
<span className={cn("text-primary font-mono", textSizes[size])}>{">"}</span>
|
|
<div className={cn("bg-primary animate-[blink_1s_step-end_infinite]", cursorSizes[size])} />
|
|
<span className="sr-only">Loading</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function TextBlinkLoader({
|
|
text = "Thinking",
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
text?: string;
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const textSizes = {
|
|
sm: "text-xs",
|
|
md: "text-sm",
|
|
lg: "text-base",
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"animate-[text-blink_2s_ease-in-out_infinite] font-medium",
|
|
textSizes[size],
|
|
className
|
|
)}
|
|
>
|
|
{text}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function TextShimmerLoader({
|
|
text = "Thinking",
|
|
className,
|
|
size = "md",
|
|
}: {
|
|
text?: string;
|
|
className?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const textSizes = {
|
|
sm: "text-xs",
|
|
md: "text-sm",
|
|
lg: "text-base",
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"bg-[linear-gradient(to_right,var(--muted-foreground)_40%,var(--foreground)_60%,var(--muted-foreground)_80%)]",
|
|
"bg-size-[200%_auto] bg-clip-text font-medium text-transparent",
|
|
"animate-[shimmer_4s_infinite_linear]",
|
|
textSizes[size],
|
|
className
|
|
)}
|
|
>
|
|
{text}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function TextDotsLoader({
|
|
className,
|
|
text = "Thinking",
|
|
size = "md",
|
|
}: {
|
|
className?: string;
|
|
text?: string;
|
|
size?: "sm" | "md" | "lg";
|
|
}) {
|
|
const textSizes = {
|
|
sm: "text-xs",
|
|
md: "text-sm",
|
|
lg: "text-base",
|
|
};
|
|
|
|
return (
|
|
<div className={cn("inline-flex items-center", className)}>
|
|
<span className={cn("text-primary font-medium", textSizes[size])}>{text}</span>
|
|
<span className="inline-flex">
|
|
<span className="text-primary animate-[loading-dots_1.4s_infinite_0.2s]">.</span>
|
|
<span className="text-primary animate-[loading-dots_1.4s_infinite_0.4s]">.</span>
|
|
<span className="text-primary animate-[loading-dots_1.4s_infinite_0.6s]">.</span>
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Loader({ variant = "circular", size = "md", text, className }: LoaderProps) {
|
|
switch (variant) {
|
|
case "circular":
|
|
return <CircularLoader size={size} className={className} />;
|
|
case "classic":
|
|
return <ClassicLoader size={size} className={className} />;
|
|
case "pulse":
|
|
return <PulseLoader size={size} className={className} />;
|
|
case "pulse-dot":
|
|
return <PulseDotLoader size={size} className={className} />;
|
|
case "dots":
|
|
return <DotsLoader size={size} className={className} />;
|
|
case "typing":
|
|
return <TypingLoader size={size} className={className} />;
|
|
case "wave":
|
|
return <WaveLoader size={size} className={className} />;
|
|
case "bars":
|
|
return <BarsLoader size={size} className={className} />;
|
|
case "terminal":
|
|
return <TerminalLoader size={size} className={className} />;
|
|
case "text-blink":
|
|
return <TextBlinkLoader text={text} size={size} className={className} />;
|
|
case "text-shimmer":
|
|
return <TextShimmerLoader text={text} size={size} className={className} />;
|
|
case "loading-dots":
|
|
return <TextDotsLoader text={text} size={size} className={className} />;
|
|
default:
|
|
return <CircularLoader size={size} className={className} />;
|
|
}
|
|
}
|
|
|
|
export { Loader };
|