mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 21:07:40 +01:00
Replace running chat spinner with paperclip animation
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
a8e4bfbdeb
commit
f37500bd07
5 files changed
+97
-26
No files matched your search
@@ -0,0 +1,23 @@
|
||||
import type { SVGProps } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
export function AnimatedPaperclipIcon({ className, ...props }: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="-1 -1 26 26"
|
||||
className={cn("paperclip-thinking-icon", className)}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
className="paperclip-thinking-icon-path"
|
||||
d="M16 6 l-8.414 8.586 a2.000 2.000 0 0 0 2.828 2.828 l8.414 -8.586 a4.000 4.000 0 1 0 -5.657 -5.657 l-8.379 8.551 a6.000 6.000 0 1 0 8.485 8.485 l8.379 -8.551"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -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)}
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 text-sm font-medium text-foreground/80">
|
||||
{agentIcon ? (
|
||||
{isActive ? (
|
||||
<AnimatedPaperclipIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
) : agentIcon ? (
|
||||
<AgentIcon icon={agentIcon} className="h-4 w-4 shrink-0" />
|
||||
) : isActive ? (
|
||||
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
|
||||
) : (
|
||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500/70" />
|
||||
@@ -1021,7 +1022,7 @@ function IssueChatRollingToolPart({ toolParts }: { toolParts: ToolCallMessagePar
|
||||
<div className="flex gap-2 px-1">
|
||||
<div className="flex flex-col items-center pt-0.5">
|
||||
{isRunning ? (
|
||||
<Loader2 className="h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground/50" />
|
||||
<AnimatedPaperclipIcon className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50" />
|
||||
) : (
|
||||
<ToolIcon className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50" />
|
||||
)}
|
||||
@@ -1792,7 +1793,7 @@ function IssueChatAssistantMessage({
|
||||
) : null}
|
||||
{isRunning ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-cyan-400/40 bg-cyan-500/10 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-cyan-700 dark:text-cyan-200">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
<AnimatedPaperclipIcon className="h-3 w-3" />
|
||||
Running
|
||||
</span>
|
||||
) : null}
|
||||
@@ -1810,11 +1811,7 @@ function IssueChatAssistantMessage({
|
||||
{message.content.length === 0 && waitingText ? (
|
||||
<div className="flex items-center gap-2.5 rounded-lg px-1 py-2">
|
||||
<span className="inline-flex items-center gap-2 text-sm font-medium text-foreground/80">
|
||||
{agentIcon ? (
|
||||
<AgentIcon icon={agentIcon} className="h-4 w-4 shrink-0" />
|
||||
) : (
|
||||
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
|
||||
)}
|
||||
<AnimatedPaperclipIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<span className="shimmer-text">{waitingText}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -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)}
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 text-sm font-medium text-foreground/80">
|
||||
{agentIcon ? (
|
||||
{isActive ? (
|
||||
<AnimatedPaperclipIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
) : agentIcon ? (
|
||||
<AgentIcon icon={agentIcon} className="h-4 w-4 shrink-0" />
|
||||
) : isActive ? (
|
||||
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
|
||||
) : (
|
||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500/70" />
|
||||
@@ -1046,7 +1047,7 @@ function IssueChatRollingToolPart({ toolParts }: { toolParts: ToolCallMessagePar
|
||||
<div className="flex gap-2 px-1">
|
||||
<div className="flex flex-col items-center pt-0.5">
|
||||
{isRunning ? (
|
||||
<Loader2 className="h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground/50" />
|
||||
<AnimatedPaperclipIcon className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50" />
|
||||
) : (
|
||||
<ToolIcon className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50" />
|
||||
)}
|
||||
@@ -1642,7 +1643,7 @@ function IssueChatAssistantMessage({
|
||||
) : null}
|
||||
{isRunning ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-cyan-400/40 bg-cyan-500/10 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-cyan-700 dark:text-cyan-200">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
<AnimatedPaperclipIcon className="h-3 w-3" />
|
||||
Running
|
||||
</span>
|
||||
) : null}
|
||||
@@ -1660,11 +1661,7 @@ function IssueChatAssistantMessage({
|
||||
{message.content.length === 0 && waitingText ? (
|
||||
<div className="flex items-center gap-2.5 rounded-lg px-1 py-2">
|
||||
<span className="inline-flex items-center gap-2 text-sm font-medium text-foreground/80">
|
||||
{agentIcon ? (
|
||||
<AgentIcon icon={agentIcon} className="h-4 w-4 shrink-0" />
|
||||
) : (
|
||||
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
|
||||
)}
|
||||
<AnimatedPaperclipIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<span className="shimmer-text">{waitingText}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -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" ? (
|
||||
<Check className={iconClass} />
|
||||
) : (
|
||||
<Wrench className={iconClass} />
|
||||
<AnimatedPaperclipIcon className={iconClass} />
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||
@@ -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",
|
||||
)}
|
||||
>
|
||||
<TerminalSquare className="h-3.5 w-3.5" />
|
||||
{isRunning ? (
|
||||
<AnimatedPaperclipIcon className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<TerminalSquare className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -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",
|
||||
)}
|
||||
>
|
||||
<Wrench className="h-3.5 w-3.5" />
|
||||
{isItemRunning ? (
|
||||
<AnimatedPaperclipIcon className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Wrench className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
@@ -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",
|
||||
)}>
|
||||
<Wrench className="h-3 w-3" />
|
||||
{item.status === "running" ? (
|
||||
<AnimatedPaperclipIcon className="h-3 w-3" />
|
||||
) : (
|
||||
<Wrench className="h-3 w-3" />
|
||||
)}
|
||||
</span>
|
||||
<span className={cn("text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground")}>
|
||||
{humanizeLabel(item.name)}
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in new issue
Block a user