diff --git a/ui/src/components/AnimatedPaperclipIcon.tsx b/ui/src/components/AnimatedPaperclipIcon.tsx new file mode 100644 index 00000000000..bb3b624ee6c --- /dev/null +++ b/ui/src/components/AnimatedPaperclipIcon.tsx @@ -0,0 +1,23 @@ +import type { SVGProps } from "react"; +import { cn } from "../lib/utils"; + +export function AnimatedPaperclipIcon({ className, ...props }: SVGProps) { + return ( + + ); +} diff --git a/ui/src/components/IssueChatThread.tsx b/ui/src/components/IssueChatThread.tsx index d0603968f1c..a4b5cf75b22 100644 --- a/ui/src/components/IssueChatThread.tsx +++ b/ui/src/components/IssueChatThread.tsx @@ -167,6 +167,7 @@ import { IssueBlockedNotice } from "./IssueBlockedNotice"; import { IssueAssignedBacklogNotice } from "./IssueAssignedBacklogNotice"; import { IssueRecoveryActionCard, type RecoveryResolveOutcome } from "./IssueRecoveryActionCard"; import { SourceTrustBadge } from "./SourceTrustBadge"; +import { AnimatedPaperclipIcon } from "./AnimatedPaperclipIcon"; interface IssueChatMessageContext { feedbackDataSharingPreference: FeedbackDataSharingPreference; @@ -893,10 +894,10 @@ function IssueChatChainOfThought({ onClick={() => hasContent && setExpanded((v) => !v)} > - {agentIcon ? ( + {isActive ? ( + + ) : agentIcon ? ( - ) : isActive ? ( - ) : ( @@ -1021,7 +1022,7 @@ function IssueChatRollingToolPart({ toolParts }: { toolParts: ToolCallMessagePar
{isRunning ? ( - + ) : ( )} @@ -1792,7 +1793,7 @@ function IssueChatAssistantMessage({ ) : null} {isRunning ? ( - + Running ) : null} @@ -1810,11 +1811,7 @@ function IssueChatAssistantMessage({ {message.content.length === 0 && waitingText ? (
- {agentIcon ? ( - - ) : ( - - )} + {waitingText}
diff --git a/ui/src/components/IssueChatThreadClassic.tsx b/ui/src/components/IssueChatThreadClassic.tsx index 1763bef99c2..22f61e3d41b 100644 --- a/ui/src/components/IssueChatThreadClassic.tsx +++ b/ui/src/components/IssueChatThreadClassic.tsx @@ -154,6 +154,7 @@ import { IssueBlockedNotice } from "./IssueBlockedNotice"; import { IssueAssignedBacklogNotice } from "./IssueAssignedBacklogNotice"; import { IssueRecoveryActionCard, type RecoveryResolveOutcome } from "./IssueRecoveryActionCard"; import { SourceTrustBadge } from "./SourceTrustBadge"; +import { AnimatedPaperclipIcon } from "./AnimatedPaperclipIcon"; interface IssueChatMessageContext { feedbackDataSharingPreference: FeedbackDataSharingPreference; @@ -918,10 +919,10 @@ function IssueChatChainOfThought({ onClick={() => hasContent && setExpanded((v) => !v)} > - {agentIcon ? ( + {isActive ? ( + + ) : agentIcon ? ( - ) : isActive ? ( - ) : ( @@ -1046,7 +1047,7 @@ function IssueChatRollingToolPart({ toolParts }: { toolParts: ToolCallMessagePar
{isRunning ? ( - + ) : ( )} @@ -1642,7 +1643,7 @@ function IssueChatAssistantMessage({ ) : null} {isRunning ? ( - + Running ) : null} @@ -1660,11 +1661,7 @@ function IssueChatAssistantMessage({ {message.content.length === 0 && waitingText ? (
- {agentIcon ? ( - - ) : ( - - )} + {waitingText}
diff --git a/ui/src/components/transcript/RunTranscriptView.tsx b/ui/src/components/transcript/RunTranscriptView.tsx index a9130f8cde8..7754a9cd60e 100644 --- a/ui/src/components/transcript/RunTranscriptView.tsx +++ b/ui/src/components/transcript/RunTranscriptView.tsx @@ -12,6 +12,7 @@ import { User, Wrench, } from "lucide-react"; +import { AnimatedPaperclipIcon } from "../AnimatedPaperclipIcon"; export type TranscriptMode = "nice" | "raw"; export type TranscriptDensity = "comfortable" | "compact"; @@ -742,7 +743,7 @@ function TranscriptToolCard({ ) : block.status === "completed" ? ( ) : ( - + )}
@@ -855,10 +856,13 @@ function TranscriptCommandGroup({ isRunning ? "border-cyan-500/25 bg-cyan-500/[0.08] text-cyan-600 dark:text-cyan-300" : "border-border/70 bg-background text-foreground/55", - isRunning && "animate-pulse", )} > - + {isRunning ? ( + + ) : ( + + )} ))}
@@ -980,10 +984,13 @@ function TranscriptToolGroup({ : isItemError ? "border-red-500/25 bg-red-500/[0.08] text-red-600 dark:text-red-300" : "border-border/70 bg-background text-foreground/55", - isItemRunning && "animate-pulse", )} > - + {isItemRunning ? ( + + ) : ( + + )} ); })} @@ -1020,7 +1027,11 @@ function TranscriptToolGroup({ ? "border-cyan-500/25 bg-cyan-500/[0.08] text-cyan-600 dark:text-cyan-300" : "border-border/70 bg-background text-foreground/55", )}> - + {item.status === "running" ? ( + + ) : ( + + )} {humanizeLabel(item.name)} diff --git a/ui/src/index.css b/ui/src/index.css index 6c5e2b7c581..5d7ab145057 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -464,12 +464,55 @@ animation: shimmer-text-slide 2.5s linear infinite; } +.paperclip-thinking-icon { + transform-origin: 50% 50%; +} + +@keyframes paperclip-thinking-draw { + 0% { + stroke-dasharray: 0 85.717; + stroke-dashoffset: -85.717; + opacity: 1; + animation-timing-function: cubic-bezier(0.455, 0.03, 0.515, 0.955); + } + 39.0625% { + stroke-dasharray: 85.717 85.717; + stroke-dashoffset: 0; + opacity: 1; + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + 78.125% { + stroke-dasharray: 0 85.717; + stroke-dashoffset: 0; + opacity: 1; + } + 78.225% { + opacity: 0; + } + 100% { + stroke-dasharray: 0 85.717; + stroke-dashoffset: 0; + opacity: 0; + } +} + +.paperclip-thinking-icon-path { + animation: paperclip-thinking-draw 1s linear infinite; +} + @media (prefers-reduced-motion: reduce) { .shimmer-text { animation: none; -webkit-text-fill-color: unset; background: none; } + + .paperclip-thinking-icon-path { + animation: none; + opacity: 1; + stroke-dasharray: 85.717 85.717; + stroke-dashoffset: 0; + } } /* Agent heartbeat capsule motion (PAP-75).