mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-29 02:46:25 +02:00
feat: add TextShimmerLoader component for enhanced loading states
- Introduced a new TextShimmerLoader component to provide a shimmer effect for loading text, improving user experience during in-progress states. - Updated the TodoItem component to utilize the TextShimmerLoader for displaying labels of in-progress tasks, replacing the previous pulse animation.
This commit is contained in:
parent
2cb5ce3aa9
commit
8a32a310f8
2 changed files with 96 additions and 20 deletions
74
surfsense_web/components/prompt-kit/loader.tsx
Normal file
74
surfsense_web/components/prompt-kit/loader.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface LoaderProps {
|
||||
variant?: "text-shimmer";
|
||||
size?: "sm" | "md" | "lg";
|
||||
text?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const textSizes = {
|
||||
sm: "text-xs",
|
||||
md: "text-sm",
|
||||
lg: "text-base",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* TextShimmerLoader - A text loader with a shimmer gradient animation
|
||||
* Used for in-progress states in write_todos and chain-of-thought
|
||||
*/
|
||||
export function TextShimmerLoader({
|
||||
text = "Thinking",
|
||||
className,
|
||||
size = "md",
|
||||
}: {
|
||||
text?: string;
|
||||
className?: string;
|
||||
size?: "sm" | "md" | "lg";
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 50%; }
|
||||
100% { background-position: -200% 50%; }
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<span
|
||||
className={cn(
|
||||
"bg-[linear-gradient(to_right,var(--muted-foreground)_40%,var(--foreground)_60%,var(--muted-foreground)_80%)]",
|
||||
"bg-[length:200%_auto] bg-clip-text font-medium text-transparent",
|
||||
"animate-[shimmer_4s_infinite_linear]",
|
||||
textSizes[size],
|
||||
className
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader component - currently only supports text-shimmer variant
|
||||
* Can be extended with more variants if needed in the future
|
||||
*/
|
||||
export function Loader({
|
||||
variant = "text-shimmer",
|
||||
size = "md",
|
||||
text,
|
||||
className,
|
||||
}: LoaderProps) {
|
||||
switch (variant) {
|
||||
case "text-shimmer":
|
||||
default:
|
||||
return (
|
||||
<TextShimmerLoader text={text} size={size} className={className} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
PartyPopper,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import type { FC } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
Accordion,
|
||||
|
|
@ -18,6 +18,7 @@ import {
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Action, ActionsConfig } from "../shared/schema";
|
||||
|
|
@ -73,24 +74,33 @@ interface TodoItemProps {
|
|||
|
||||
const TodoItem: FC<TodoItemProps> = ({ todo, isStreaming = true }) => {
|
||||
const isStrikethrough = todo.status === "completed" || todo.status === "cancelled";
|
||||
// Only show pulse animation if streaming and in progress
|
||||
// Only show shimmer animation if streaming and in progress
|
||||
const isShimmer = todo.status === "in_progress" && isStreaming;
|
||||
|
||||
// Render the label with optional shimmer effect
|
||||
const renderLabel = () => {
|
||||
if (isShimmer) {
|
||||
return <TextShimmerLoader text={todo.label} size="md" />;
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
isStrikethrough && "line-through text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{todo.label}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
if (todo.description) {
|
||||
return (
|
||||
<AccordionItem value={todo.id} className="border-0">
|
||||
<AccordionTrigger className="py-2 hover:no-underline">
|
||||
<div className="flex items-center gap-2">
|
||||
<StatusIcon status={todo.status} isStreaming={isStreaming} />
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm text-left",
|
||||
isStrikethrough && "line-through text-muted-foreground",
|
||||
isShimmer && "animate-pulse"
|
||||
)}
|
||||
>
|
||||
{todo.label}
|
||||
</span>
|
||||
{renderLabel()}
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="pb-2 pl-6">
|
||||
|
|
@ -103,15 +113,7 @@ const TodoItem: FC<TodoItemProps> = ({ todo, isStreaming = true }) => {
|
|||
return (
|
||||
<div className="flex items-center gap-2 py-2">
|
||||
<StatusIcon status={todo.status} isStreaming={isStreaming} />
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
isStrikethrough && "line-through text-muted-foreground",
|
||||
isShimmer && "animate-pulse"
|
||||
)}
|
||||
>
|
||||
{todo.label}
|
||||
</span>
|
||||
{renderLabel()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue