diff --git a/src/ui/src/components/common/LoadingState.tsx b/src/ui/src/components/common/LoadingState.tsx index 54e621b9..7143890a 100644 --- a/src/ui/src/components/common/LoadingState.tsx +++ b/src/ui/src/components/common/LoadingState.tsx @@ -1,9 +1,28 @@ import { Spinner } from "../ui/spinner"; -export function LoadingState() { +interface LoadingStateProps { + /** + * Extra classes applied to the spinner icon itself (e.g. "mr-2 h-4 w-4") so callers that + * previously rendered a bare `Loader2` icon inline can preserve their sizing/margin. + */ + className?: string; + /** + * When set, skips the default centered block wrapper (`flex items-center justify-center + * py-8`) so the spinner sits inline next to a label instead of taking over a full block. + * Callers migrating a small inline `Loader2` (inside a button, next to text, etc.) should + * pass `true`; full-page/section loaders should leave this unset to keep prior behaviour. + */ + inline?: boolean; +} + +export function LoadingState({ className, inline = false }: LoadingStateProps = {}) { + if (inline) { + return ; + } + return (
- +
); } diff --git a/src/ui/src/components/health-status/health-status.tsx b/src/ui/src/components/health-status/health-status.tsx index 02ffdf05..11f71e12 100644 --- a/src/ui/src/components/health-status/health-status.tsx +++ b/src/ui/src/components/health-status/health-status.tsx @@ -1,6 +1,7 @@ -import { Ban, CheckCircle2, Loader2, TriangleAlert, X } from "lucide-react"; +import { Ban, CheckCircle2, Loader2, TriangleAlert, X,XCircle } from "lucide-react"; import * as React from "react"; import { cn } from "@/lib/utils"; +import { LoadingState } from "../common/LoadingState"; interface SuccessDetails { username?: string; @@ -68,7 +69,7 @@ export const HealthStatus = React.forwardRef( className={cn("group relative flex items-center gap-2", className)} {...props} > - + Checking... diff --git a/src/ui/src/components/settings/llm/LLMProviderForm.tsx b/src/ui/src/components/settings/llm/LLMProviderForm.tsx index 60c6b999..8140dc38 100644 --- a/src/ui/src/components/settings/llm/LLMProviderForm.tsx +++ b/src/ui/src/components/settings/llm/LLMProviderForm.tsx @@ -1,10 +1,10 @@ import type { ModelConfig } from "@shared/types/llm"; -import { Loader2 } from "lucide-react"; import { useState } from "react"; import type { UseFormReturn } from "react-hook-form"; import { DeleteConfirmDialog } from "@/components/dialogs/DeleteConfirmDialog"; import { LLMProviderFields, type ProviderOption } from "@/components/llm/LLMProviderFields"; import type { HealthStatusInfo } from "../../../types"; +import { LoadingState } from "../../common/LoadingState"; import { Button } from "../../ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../../ui/form"; import { Input } from "../../ui/input"; @@ -81,7 +81,7 @@ export function LLMProviderForm({ Clear Config diff --git a/src/ui/src/components/settings/llm/OrchestratorBackendSetting.tsx b/src/ui/src/components/settings/llm/OrchestratorBackendSetting.tsx index 999568b3..c01593b0 100644 --- a/src/ui/src/components/settings/llm/OrchestratorBackendSetting.tsx +++ b/src/ui/src/components/settings/llm/OrchestratorBackendSetting.tsx @@ -1,8 +1,8 @@ import type { LLMConfigV2, OrchestrationBackend, OrchestratorReadiness } from "@shared/types/llm"; import { DEFAULT_ORCHESTRATION_BACKEND } from "@shared/types/llm"; -import { Loader2 } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; +import { LoadingState } from "@/components/common/LoadingState"; import { Button } from "@/components/ui/button"; import { Select, @@ -207,7 +207,7 @@ export function OrchestratorBackendSetting({ isActive }: OrchestratorBackendSett onClick={() => void checkReadiness()} disabled={isCheckingReadiness} > - {isCheckingReadiness && } + {isCheckingReadiness && } Re-check } diff --git a/src/ui/src/components/video/UploadResult.tsx b/src/ui/src/components/video/UploadResult.tsx index 3764a491..c933efcb 100644 --- a/src/ui/src/components/video/UploadResult.tsx +++ b/src/ui/src/components/video/UploadResult.tsx @@ -1,8 +1,9 @@ -import { Copy, ExternalLink, Loader2 } from "lucide-react"; +import { Copy, ExternalLink } from "lucide-react"; import { toast } from "sonner"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { useClipboard } from "../../hooks/useClipboard"; import { UploadStatus, type VideoUploadResult } from "../../types"; +import { LoadingState } from "../common/LoadingState"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; @@ -16,7 +17,7 @@ const openUrl = (url: string | null) => { const UploadingBadge = () => ( - + Uploading ); diff --git a/src/ui/src/components/workflow/ShaveOutcomeView.tsx b/src/ui/src/components/workflow/ShaveOutcomeView.tsx index 92eaa561..ad10ed16 100644 --- a/src/ui/src/components/workflow/ShaveOutcomeView.tsx +++ b/src/ui/src/components/workflow/ShaveOutcomeView.tsx @@ -1,4 +1,4 @@ -import { AlertTriangle, ExternalLink, Loader2 } from "lucide-react"; +import { AlertTriangle, ExternalLink } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { getStatusVariant } from "@/lib/shave-utils"; import { ipcClient } from "../../services/ipc-client"; @@ -121,7 +121,7 @@ export function ShaveOutcomeView({ shaveId }: ShaveOutcomeViewProps) { {isProcessing && (
- + This shave is still running

diff --git a/src/ui/src/components/workflow/UndoStagePanel.tsx b/src/ui/src/components/workflow/UndoStagePanel.tsx index b468866f..0f8e8c39 100644 --- a/src/ui/src/components/workflow/UndoStagePanel.tsx +++ b/src/ui/src/components/workflow/UndoStagePanel.tsx @@ -1,7 +1,8 @@ -import { Check, ChevronRight, Loader2, Undo2, Wrench, X } from "lucide-react"; +import { Check, ChevronRight, Undo2, Wrench, X } from "lucide-react"; import type React from "react"; import { type MCPStep, MCPStepType } from "../../types"; import { deepParseJson, formatToolName } from "../../utils"; +import { LoadingState } from "../common/LoadingState"; import { ReasoningStep } from "./ReasoningStep"; const handleDetailsToggle = (data: unknown) => (e: React.SyntheticEvent) => { @@ -72,7 +73,7 @@ interface UndoStagePanelProps { const StatusBadge = ({ status }: { status: UndoStagePanelProps["status"] }) => { if (status === "in_progress") { - return ; + return ; } if (status === "completed") { return ; diff --git a/src/ui/src/components/workflow/WorkflowStepCard.tsx b/src/ui/src/components/workflow/WorkflowStepCard.tsx index 7d150fb1..d4ddecfd 100644 --- a/src/ui/src/components/workflow/WorkflowStepCard.tsx +++ b/src/ui/src/components/workflow/WorkflowStepCard.tsx @@ -4,7 +4,6 @@ import { CheckCircle2, ChevronDown, ChevronRight, - Loader2, RefreshCw, Sparkles, XCircle, @@ -15,6 +14,7 @@ import { cn } from "@/lib/utils"; import { formatErrorMessage, isErrorStep } from "@/utils"; import { ipcClient } from "../../services/ipc-client"; import type { MCPStep } from "../../types"; +import { LoadingState } from "../common/LoadingState"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; import { Card, CardContent } from "../ui/card"; @@ -60,8 +60,8 @@ function OrchestratorBadge({ backend }: { backend: OrchestratorBackend }) { const STATUS_CONFIG = { in_progress: { - icon: Loader2, - iconClass: "animate-spin text-zinc-300", + icon: null, + iconClass: "text-zinc-300", containerClass: "border-gray-500/30 bg-gray-500/5", textClass: "text-white/90", }, @@ -87,6 +87,11 @@ const STATUS_CONFIG = { function StatusIcon({ status, className }: { status: WorkflowStep["status"]; className?: string }) { const config = STATUS_CONFIG[status as keyof typeof STATUS_CONFIG] || STATUS_CONFIG.not_started; + + if (status === "in_progress") { + return ; + } + const Icon = config.icon; if (!Icon) { @@ -271,7 +276,7 @@ export function WorkflowStepCard({ step, label, shaveId }: WorkflowStepCardProps className="bg-white/[0.08] border border-white/[0.15] hover:bg-white/[0.12] text-white/80 shrink-0" > {isRetrying ? ( - + ) : ( <>