mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-09 07:42:39 +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,
|
PartyPopper,
|
||||||
XCircle,
|
XCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { FC, ReactNode } from "react";
|
import type { FC } from "react";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
|
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { Action, ActionsConfig } from "../shared/schema";
|
import type { Action, ActionsConfig } from "../shared/schema";
|
||||||
|
|
@ -73,24 +74,33 @@ interface TodoItemProps {
|
||||||
|
|
||||||
const TodoItem: FC<TodoItemProps> = ({ todo, isStreaming = true }) => {
|
const TodoItem: FC<TodoItemProps> = ({ todo, isStreaming = true }) => {
|
||||||
const isStrikethrough = todo.status === "completed" || todo.status === "cancelled";
|
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;
|
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) {
|
if (todo.description) {
|
||||||
return (
|
return (
|
||||||
<AccordionItem value={todo.id} className="border-0">
|
<AccordionItem value={todo.id} className="border-0">
|
||||||
<AccordionTrigger className="py-2 hover:no-underline">
|
<AccordionTrigger className="py-2 hover:no-underline">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<StatusIcon status={todo.status} isStreaming={isStreaming} />
|
<StatusIcon status={todo.status} isStreaming={isStreaming} />
|
||||||
<span
|
{renderLabel()}
|
||||||
className={cn(
|
|
||||||
"text-sm text-left",
|
|
||||||
isStrikethrough && "line-through text-muted-foreground",
|
|
||||||
isShimmer && "animate-pulse"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{todo.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="pb-2 pl-6">
|
<AccordionContent className="pb-2 pl-6">
|
||||||
|
|
@ -103,15 +113,7 @@ const TodoItem: FC<TodoItemProps> = ({ todo, isStreaming = true }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 py-2">
|
<div className="flex items-center gap-2 py-2">
|
||||||
<StatusIcon status={todo.status} isStreaming={isStreaming} />
|
<StatusIcon status={todo.status} isStreaming={isStreaming} />
|
||||||
<span
|
{renderLabel()}
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
isStrikethrough && "line-through text-muted-foreground",
|
|
||||||
isShimmer && "animate-pulse"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{todo.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue