diff --git a/packages/chat-ui/src/styles.css b/packages/chat-ui/src/styles.css index 45237d7393..4e82c63499 100644 --- a/packages/chat-ui/src/styles.css +++ b/packages/chat-ui/src/styles.css @@ -2561,6 +2561,50 @@ background: color-mix(in srgb, var(--foreground) 55%, transparent); } +.chat-silk-thinking-indicator { + display: flex; + align-items: center; + gap: 0.75rem; + color: var(--muted-foreground); + font-size: 0.875rem; +} + +.chat-silk-thinking-mark { + width: 1.875rem; + height: auto; + overflow: visible; + flex: 0 0 auto; +} + +.chat-silk-thinking-mark-base { + fill: var(--muted-foreground); +} + +.chat-silk-thinking-mark-active { + fill: var(--primary); +} + +.chat-silk-beam-x { + animation: chat-silk-sweep 1.45s cubic-bezier(0.22, 0.05, 0.38, 1) infinite; +} + +@keyframes chat-silk-sweep { + 0% { + transform: translateX(8px); + opacity: 0; + } + 8% { + opacity: 1; + } + 86% { + opacity: 1; + } + 100% { + transform: translateX(130px); + opacity: 0; + } +} + @media (prefers-reduced-motion: no-preference) { .chat-typing-indicator-dots span { animation: chat-typing-dot 1.2s ease-in-out infinite; @@ -2575,6 +2619,14 @@ } } +@media (prefers-reduced-motion: reduce) { + .chat-silk-beam-x { + animation: none; + opacity: 1; + transform: translateX(44px); + } +} + @keyframes chat-typing-dot { 0%, 60%, diff --git a/packages/chat-ui/src/typing-indicator.tsx b/packages/chat-ui/src/typing-indicator.tsx index 868adfc060..2749ea2c6d 100644 --- a/packages/chat-ui/src/typing-indicator.tsx +++ b/packages/chat-ui/src/typing-indicator.tsx @@ -6,7 +6,8 @@ // forwards that event type (see use-workbench-stream.ts) — so this is wiring // an existing read, not new realtime backend. -import { useEffect, useRef, useState } from "react"; +import { CORBITS_MARK_PATH } from "@corbits/react-ui"; +import { useEffect, useId, useRef, useState } from "react"; import type { ParticipantRecord } from "./api"; import { localPartOf, type CurrentUser } from "./timeline"; @@ -166,6 +167,69 @@ function TypingDotsBubble({ status }: { readonly status: string }) { ); } +function SilkThinkingIndicator({ status }: { readonly status: string }) { + const gradientId = useId(); + const maskId = useId(); + + return ( +