feat: replace Loader2 with Spinner component for consistent loading indicators

This commit is contained in:
Anish Sarkar 2026-01-25 15:23:45 +05:30
parent 9215118bab
commit 2d17d1a1b6
34 changed files with 113 additions and 105 deletions

View file

@ -7,12 +7,13 @@ import {
useAssistantApi,
useAssistantState,
} from "@assistant-ui/react";
import { FileText, Loader2, Paperclip, PlusIcon, Upload, XIcon } from "lucide-react";
import { FileText, Paperclip, PlusIcon, Upload, XIcon } from "lucide-react";
import Image from "next/image";
import { type FC, type PropsWithChildren, useEffect, useRef, useState } from "react";
import { useShallow } from "zustand/shallow";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Spinner } from "@/components/ui/spinner";
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import {
DropdownMenu,
@ -135,7 +136,7 @@ const AttachmentThumb: FC = () => {
if (isProcessing) {
return (
<div className="flex h-full w-full items-center justify-center bg-muted">
<Loader2 className="size-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
);
}
@ -213,7 +214,7 @@ const AttachmentUI: FC = () => {
>
{isProcessing ? (
<span className="flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Processing...
</span>
) : (

View file

@ -1,8 +1,8 @@
"use client";
import { Loader2 } from "lucide-react";
import type { FC } from "react";
import { cn } from "@/lib/utils";
import { Spinner } from "@/components/ui/spinner";
interface ChatSessionStatusProps {
isAiResponding: boolean;
@ -43,7 +43,7 @@ export const ChatSessionStatus: FC<ChatSessionStatusProps> = ({
className
)}
>
<Loader2 className="size-3.5 animate-spin" />
<Spinner size="xs" />
<span>Currently responding to {displayName}</span>
</div>
);

View file

@ -1,13 +1,14 @@
"use client";
import { useAtomValue } from "jotai";
import { Cable, Loader2 } from "lucide-react";
import { Cable } from "lucide-react";
import { useSearchParams } from "next/navigation";
import type { FC } from "react";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { Spinner } from "@/components/ui/spinner";
import { Tabs, TabsContent } from "@/components/ui/tabs";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { useConnectorsElectric } from "@/hooks/use-connectors-electric";
@ -174,7 +175,7 @@ export const ConnectorIndicator: FC = () => {
onClick={() => handleOpenChange(true)}
>
{isLoading ? (
<Loader2 className="size-4 animate-spin" />
<Spinner size="sm" />
) : (
<>
<Cable className="size-4 stroke-[1.5px]" />

View file

@ -1,9 +1,10 @@
"use client";
import { IconBrandYoutube } from "@tabler/icons-react";
import { FileText, Loader2 } from "lucide-react";
import { FileText } from "lucide-react";
import type { FC } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { cn } from "@/lib/utils";
@ -111,7 +112,7 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
</div>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Syncing
</p>
) : isConnected ? (
@ -151,7 +152,7 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
disabled={isConnecting || !isEnabled}
>
{isConnecting ? (
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
) : !isEnabled ? (
"Unavailable"
) : isConnected ? (

View file

@ -1,8 +1,9 @@
"use client";
import { ArrowLeft, Loader2 } from "lucide-react";
import { ArrowLeft } from "lucide-react";
import { type FC, useMemo } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import type { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { getConnectorTypeDisplay } from "@/lib/connectors/utils";
@ -139,7 +140,7 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Connecting
</>
) : connectorType === "MCP_CONNECTOR" ? (

View file

@ -1,8 +1,9 @@
"use client";
import { ArrowLeft, Info, Loader2, RefreshCw, Trash2 } from "lucide-react";
import { ArrowLeft, Info, RefreshCw, Trash2 } from "lucide-react";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { cn } from "@/lib/utils";
@ -311,7 +312,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
>
{isDisconnecting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Disconnecting
</>
) : (
@ -347,7 +348,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
>
{isSaving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Saving
</>
) : (

View file

@ -1,9 +1,10 @@
"use client";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { ArrowLeft, Check, Info } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { getConnectorTypeDisplay } from "@/lib/connectors/utils";
import { cn } from "@/lib/utils";
@ -216,7 +217,7 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
>
{isStartingIndexing ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Starting...
</>
) : (

View file

@ -1,11 +1,12 @@
"use client";
import { ArrowRight, Cable, Loader2 } from "lucide-react";
import { ArrowRight, Cable } from "lucide-react";
import { useRouter } from "next/navigation";
import type { FC } from "react";
import { useState } from "react";
import { getDocumentTypeLabel } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
import { TabsContent } from "@/components/ui/tabs";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
@ -209,7 +210,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
<p className="text-[14px] font-semibold leading-tight truncate">{title}</p>
{isAnyIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Syncing
</p>
) : (
@ -270,7 +271,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
</div>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Syncing
</p>
) : !isMCPConnector ? (

View file

@ -1,9 +1,10 @@
"use client";
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns";
import { ArrowLeft, Loader2, Plus, Server } from "lucide-react";
import { ArrowLeft, Plus, Server } from "lucide-react";
import type { FC } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
@ -143,7 +144,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
>
<div className="flex h-5 w-5 items-center justify-center rounded-md bg-primary/10 shrink-0">
{isConnecting ? (
<Loader2 className="size-3 animate-spin text-primary" />
<Spinner size="xs" className="text-primary" />
) : (
<Plus className="size-3 text-primary" />
)}
@ -207,7 +208,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
</p>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Syncing
</p>
) : (

View file

@ -2,13 +2,14 @@
import { TagInput, type Tag as TagType } from "emblor";
import { useAtom } from "jotai";
import { ArrowLeft, Loader2 } from "lucide-react";
import { ArrowLeft } from "lucide-react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { type FC, useState } from "react";
import { toast } from "sonner";
import { createDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { Label } from "@/components/ui/label";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
@ -222,7 +223,7 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
{t("processing")}
</>
) : (

View file

@ -6,6 +6,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type { CommentActionsProps } from "./types";
@ -23,7 +24,7 @@ export function CommentActions({ canEdit, canDelete, onEdit, onDelete }: Comment
size="icon"
className="size-7 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity"
>
<MoreHorizontal className="size-4" />
<MoreHorizontal className="size-4 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
@ -33,8 +34,9 @@ export function CommentActions({ canEdit, canDelete, onEdit, onDelete }: Comment
Edit
</DropdownMenuItem>
)}
{canEdit && canDelete && <DropdownMenuSeparator />}
{canDelete && (
<DropdownMenuItem onClick={onDelete} className="text-destructive">
<DropdownMenuItem onClick={onDelete} className="text-destructive focus:text-destructive">
<Trash2 className="mr-2 size-4" />
Delete
</DropdownMenuItem>

View file

@ -1,7 +1,7 @@
"use client";
import { Loader2 } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Spinner } from "@/components/ui/spinner";
import { MemberMentionItem } from "./member-mention-item";
import type { MemberMentionPickerProps } from "./types";
@ -24,7 +24,7 @@ export function MemberMentionPicker({
if (isLoading) {
return (
<div className="flex items-center justify-center py-6">
<Loader2 className="size-5 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
);
}

View file

@ -10,12 +10,12 @@ import {
FolderOpen,
HardDrive,
Image,
Loader2,
Presentation,
} from "lucide-react";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Spinner } from "@/components/ui/spinner";
import { useComposioDriveFolders } from "@/hooks/use-composio-drive-folders";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { cn } from "@/lib/utils";
@ -253,7 +253,7 @@ export function ComposioDriveFolderTree({
aria-label={isExpanded ? `Collapse ${item.name}` : `Expand ${item.name}`}
>
{isLoading ? (
<Loader2 className="h-2.5 w-2.5 sm:h-3 sm:w-3 animate-spin" />
<Spinner size="xs" className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
) : isExpanded ? (
<ChevronDown className="h-3 w-3 sm:h-4 sm:w-4" />
) : (
@ -344,7 +344,7 @@ export function ComposioDriveFolderTree({
{isLoadingRoot && (
<div className="flex items-center justify-center py-4 sm:py-8">
<Loader2 className="h-4 w-4 sm:h-6 sm:w-6 animate-spin text-muted-foreground" />
<Spinner size="sm" className="sm:h-6 sm:w-6 text-muted-foreground" />
</div>
)}

View file

@ -10,12 +10,12 @@ import {
FolderOpen,
HardDrive,
Image,
Loader2,
Presentation,
} from "lucide-react";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Spinner } from "@/components/ui/spinner";
import { useGoogleDriveFolders } from "@/hooks/use-google-drive-folders";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { cn } from "@/lib/utils";
@ -253,7 +253,7 @@ export function GoogleDriveFolderTree({
aria-label={isExpanded ? `Collapse ${item.name}` : `Expand ${item.name}`}
>
{isLoading ? (
<Loader2 className="h-2.5 w-2.5 sm:h-3 sm:w-3 animate-spin" />
<Spinner size="xs" className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
) : isExpanded ? (
<ChevronDown className="h-3 w-3 sm:h-4 sm:w-4" />
) : (
@ -344,7 +344,7 @@ export function GoogleDriveFolderTree({
{isLoadingRoot && (
<div className="flex items-center justify-center py-4 sm:py-8">
<Loader2 className="h-4 w-4 sm:h-6 sm:w-6 animate-spin text-muted-foreground" />
<Spinner size="sm" className="sm:h-6 sm:w-6 text-muted-foreground" />
</div>
)}

View file

@ -2,7 +2,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { Loader2, Plus, Search } from "lucide-react";
import { Plus, Search } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { useForm } from "react-hook-form";
@ -26,6 +26,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/spinner";
import { trackSearchSpaceCreated } from "@/lib/posthog/events";
const formSchema = z.object({
@ -145,7 +146,7 @@ export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpac
<Button type="submit" disabled={isSubmitting} className="w-full sm:w-auto h-9 sm:h-10 text-sm">
{isSubmitting ? (
<>
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-1.5" />
{t("creating")}
</>
) : (

View file

@ -4,7 +4,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import { format } from "date-fns";
import {
ArchiveIcon,
Loader2,
MessageCircleMore,
MoreHorizontal,
RotateCcwIcon,
@ -30,6 +29,7 @@ import {
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Spinner } from "@/components/ui/spinner";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import {
deleteThread,
@ -304,7 +304,7 @@ export function AllPrivateChatsSidebar({
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
{isLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
) : error ? (
<div className="text-center py-8 text-sm text-destructive">
@ -365,7 +365,7 @@ export function AllPrivateChatsSidebar({
disabled={isBusy}
>
{isDeleting ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
<Spinner size="xs" />
) : (
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
)}

View file

@ -4,7 +4,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import { format } from "date-fns";
import {
ArchiveIcon,
Loader2,
MessageCircleMore,
MoreHorizontal,
RotateCcwIcon,
@ -30,6 +29,7 @@ import {
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Spinner } from "@/components/ui/spinner";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import {
deleteThread,
@ -304,7 +304,7 @@ export function AllSharedChatsSidebar({
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
{isLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
) : error ? (
<div className="text-center py-8 text-sm text-destructive">
@ -365,7 +365,7 @@ export function AllSharedChatsSidebar({
disabled={isBusy}
>
{isDeleting ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
<Spinner size="xs" />
) : (
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
)}

View file

@ -1,6 +1,5 @@
"use client";
import { Check } from "lucide-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { NavItem } from "../../types/layout.types";
@ -40,9 +39,6 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti
{...joyrideAttr}
>
<Icon className="h-4 w-4" />
{item.isActive && (
<Check className="absolute bottom-0.5 right-0.5 h-3 w-3 text-primary" />
)}
{item.badge && (
<span className="absolute top-0.5 right-0.5 inline-flex items-center justify-center min-w-[14px] h-[14px] px-0.5 rounded-full bg-red-500 text-white text-[9px] font-medium">
{item.badge}
@ -73,9 +69,6 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti
>
<Icon className="h-4 w-4 shrink-0" />
<span className="flex-1 truncate">{item.title}</span>
{item.isActive && (
<Check className="h-4 w-4 shrink-0 text-primary" />
)}
{item.badge && (
<span className="inline-flex items-center justify-center min-w-4 h-4 px-1 rounded-full bg-red-500 text-white text-[10px] font-medium">
{item.badge}

View file

@ -8,7 +8,6 @@ import {
Cloud,
Edit3,
Globe,
Loader2,
Plus,
Settings2,
Sparkles,
@ -36,6 +35,7 @@ import {
CommandSeparator,
} from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Spinner } from "@/components/ui/spinner";
import type {
GlobalNewLLMConfig,
NewLLMConfigPublic,
@ -179,7 +179,7 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
>
{isLoading ? (
<>
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
<Spinner size="sm" className="text-muted-foreground" />
<span className="text-muted-foreground hidden md:inline">Loading</span>
</>
) : currentConfig ? (

View file

@ -6,7 +6,6 @@ import {
Bot,
CheckCircle,
FileText,
Loader2,
RefreshCw,
RotateCcw,
Save,
@ -32,6 +31,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Spinner } from "@/components/ui/spinner";
const ROLE_DESCRIPTIONS = {
agent: {
@ -206,7 +206,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
<Card>
<CardContent className="flex items-center justify-center py-8 md:py-12">
<div className="flex items-center gap-2 text-muted-foreground">
<Loader2 className="w-4 h-4 md:w-5 md:h-5 animate-spin" />
<Spinner size="sm" className="md:h-5 md:w-5" />
<span className="text-xs md:text-sm">
{configsLoading && preferencesLoading
? "Loading configurations and preferences..."

View file

@ -7,7 +7,6 @@ import {
Clock,
Edit3,
FileText,
Loader2,
MessageSquareQuote,
Plus,
RefreshCw,
@ -49,6 +48,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Spinner } from "@/components/ui/spinner";
import type { NewLLMConfig } from "@/contracts/types/new-llm-config.types";
import { cn } from "@/lib/utils";
@ -211,7 +211,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
<Card>
<CardContent className="flex items-center justify-center py-10 md:py-16">
<div className="flex flex-col items-center gap-2 md:gap-3">
<Loader2 className="h-6 w-6 md:h-8 md:w-8 animate-spin text-muted-foreground" />
<Spinner size="md" className="md:h-8 md:w-8 text-muted-foreground" />
<span className="text-xs md:text-sm text-muted-foreground">
Loading configurations...
</span>
@ -484,7 +484,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
>
{isDeleting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Deleting
</>
) : (

View file

@ -8,7 +8,6 @@ import {
ChevronDown,
ChevronsUpDown,
Key,
Loader2,
MessageSquareQuote,
Rocket,
Sparkles,
@ -48,6 +47,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { getModelsByProvider } from "@/contracts/enums/llm-models";
@ -592,7 +592,7 @@ export function LLMConfigForm({
>
{isSubmitting ? (
<>
<Loader2 className="h-3.5 w-3.5 sm:h-4 sm:w-4 animate-spin" />
<Spinner size="sm" />
{mode === "edit" ? "Updating..." : "Creating"}
</>
) : (

View file

@ -1,13 +1,14 @@
"use client";
import { useAtom } from "jotai";
import { CheckCircle2, FileType, Info, Loader2, Tag, Upload, X } from "lucide-react";
import { CheckCircle2, FileType, Info, Tag, Upload, X } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useTranslations } from "next-intl";
import { useCallback, useMemo, useRef, useState } from "react";
import { useDropzone } from "react-dropzone";
import { toast } from "sonner";
import { uploadDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
import { Spinner } from "@/components/ui/spinner";
import {
Accordion,
AccordionContent,
@ -424,7 +425,7 @@ export function DocumentUploadTab({
>
{isUploading ? (
<span className="flex items-center gap-2">
<Loader2 className="h-4 w-4 sm:h-5 sm:w-5 animate-spin" />
<Spinner size="sm" />
{t("uploading")}
</span>
) : (

View file

@ -1,10 +1,11 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import { AlertCircleIcon, Loader2Icon, MicIcon } from "lucide-react";
import { AlertCircleIcon, MicIcon } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { z } from "zod";
import { Audio } from "@/components/tool-ui/audio";
import { Spinner } from "@/components/ui/spinner";
import { baseApiService } from "@/lib/apis/base-api.service";
import { authenticatedFetch } from "@/lib/auth-utils";
import { clearActivePodcastTaskId, setActivePodcastTaskId } from "@/lib/chat/podcast-state";
@ -97,8 +98,8 @@ function PodcastGeneratingState({ title }: { title: string }) {
<div className="flex-1">
<h3 className="font-semibold text-foreground text-lg">{title}</h3>
<div className="mt-2 flex items-center gap-2 text-muted-foreground">
<Loader2Icon className="size-4 animate-spin" />
<span className="text-sm">Generating podcast. This may take a few minutes</span>
<Spinner size="sm" />
<span className="text-sm">Generating podcast. This may take a few minutes.</span>
</div>
<div className="mt-3">
<div className="h-1.5 w-full overflow-hidden rounded-full bg-primary/10">
@ -144,7 +145,7 @@ function AudioLoadingState({ title }: { title: string }) {
<div className="flex-1">
<h3 className="font-semibold text-foreground">{title}</h3>
<div className="mt-2 flex items-center gap-2 text-muted-foreground">
<Loader2Icon className="size-4 animate-spin" />
<Spinner size="sm" />
<span className="text-sm">Loading audio...</span>
</div>
</div>

View file

@ -1,11 +1,12 @@
"use client";
import { ExternalLinkIcon, ImageIcon, Loader2 } from "lucide-react";
import { ExternalLinkIcon, ImageIcon } from "lucide-react";
import NextImage from "next/image";
import { Component, type ReactNode, useState } from "react";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";
/**
@ -184,7 +185,7 @@ export function ImageLoading({ title = "Loading image..." }: { title?: string })
<Card className="w-full max-w-md overflow-hidden">
<div className="aspect-[4/3] bg-muted flex items-center justify-center">
<div className="flex flex-col items-center gap-3">
<Loader2 className="size-8 text-muted-foreground animate-spin" />
<Spinner size="lg" className="text-muted-foreground" />
<p className="text-muted-foreground text-sm">{title}</p>
</div>
</div>

View file

@ -1,12 +1,13 @@
"use client";
import { ExternalLinkIcon, Globe, ImageIcon, LinkIcon, Loader2 } from "lucide-react";
import { ExternalLinkIcon, Globe, ImageIcon, LinkIcon } from "lucide-react";
import Image from "next/image";
import { Component, type ReactNode } from "react";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Spinner } from "@/components/ui/spinner";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
@ -299,18 +300,17 @@ export function MediaCard({
{/* Response Actions */}
{responseActions && responseActions.length > 0 && (
<div
className="mt-4 flex items-center justify-end gap-2 border-t pt-3"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
<div className="mt-4 flex items-center justify-end gap-2 border-t pt-3">
{responseActions.map((action) => (
<Tooltip key={action.id}>
<TooltipTrigger asChild>
<Button
variant={action.variant || "secondary"}
size="sm"
onClick={() => onResponseAction?.(action.id)}
onClick={(e) => {
e.stopPropagation();
onResponseAction?.(action.id);
}}
>
{action.label}
</Button>
@ -337,7 +337,7 @@ export function MediaCardLoading({ title = "Loading preview..." }: { title?: str
return (
<Card className="w-full max-w-md overflow-hidden">
<div className="aspect-[2/1] bg-muted animate-pulse flex items-center justify-center">
<Loader2 className="size-8 text-muted-foreground animate-spin" />
<Spinner size="lg" className="text-muted-foreground" />
</div>
<CardContent className="p-4">
<div className="flex items-center gap-3">

View file

@ -2,7 +2,6 @@
import { makeAssistantToolUI, useAssistantState } from "@assistant-ui/react";
import { useAtomValue, useSetAtom } from "jotai";
import { Loader2 } from "lucide-react";
import { useEffect, useMemo } from "react";
import { z } from "zod";
import {
@ -11,6 +10,7 @@ import {
registerPlanOwner,
updatePlanStateAtom,
} from "@/atoms/chat/plan-state.atom";
import { Spinner } from "@/components/ui/spinner";
import { Plan, PlanErrorBoundary, parseSerializablePlan, TodoStatusSchema } from "./plan";
// ============================================================================
@ -46,7 +46,7 @@ function WriteTodosLoading() {
return (
<div className="my-4 w-full max-w-xl rounded-2xl border bg-card/60 px-5 py-4 shadow-sm">
<div className="flex items-center gap-3">
<Loader2 className="size-5 animate-spin text-primary" />
<Spinner size="md" className="text-primary" />
<span className="text-sm text-muted-foreground">Creating plan...</span>
</div>
</div>