Add copilot animations

This commit is contained in:
akhisud3195 2025-04-14 14:00:06 +05:30
parent 903ed7ced4
commit 577d637c75
3 changed files with 53 additions and 4 deletions

View file

@ -29,6 +29,7 @@ interface AppProps {
dispatch: (action: any) => void; dispatch: (action: any) => void;
chatContext?: any; chatContext?: any;
onCopyJson?: (data: { messages: any[], lastRequest: any, lastResponse: any }) => void; onCopyJson?: (data: { messages: any[], lastRequest: any, lastResponse: any }) => void;
onMessagesChange?: (messages: z.infer<typeof CopilotMessage>[]) => void;
} }
const App = forwardRef<{ handleCopyChat: () => void }, AppProps>(function App({ const App = forwardRef<{ handleCopyChat: () => void }, AppProps>(function App({
@ -37,6 +38,7 @@ const App = forwardRef<{ handleCopyChat: () => void }, AppProps>(function App({
dispatch, dispatch,
chatContext = undefined, chatContext = undefined,
onCopyJson, onCopyJson,
onMessagesChange,
}, ref) { }, ref) {
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const [messages, setMessages] = useState<z.infer<typeof CopilotMessage>[]>([]); const [messages, setMessages] = useState<z.infer<typeof CopilotMessage>[]>([]);
@ -47,6 +49,11 @@ const App = forwardRef<{ handleCopyChat: () => void }, AppProps>(function App({
const [lastRequest, setLastRequest] = useState<unknown | null>(null); const [lastRequest, setLastRequest] = useState<unknown | null>(null);
const [lastResponse, setLastResponse] = useState<unknown | null>(null); const [lastResponse, setLastResponse] = useState<unknown | null>(null);
// Notify parent of message changes
useEffect(() => {
onMessagesChange?.(messages);
}, [messages, onMessagesChange]);
// Check for initial prompt in local storage and send it // Check for initial prompt in local storage and send it
useEffect(() => { useEffect(() => {
const prompt = localStorage.getItem(`project_prompt_${projectId}`); const prompt = localStorage.getItem(`project_prompt_${projectId}`);
@ -303,10 +310,12 @@ export function Copilot({
}) { }) {
const [copilotKey, setCopilotKey] = useState(0); const [copilotKey, setCopilotKey] = useState(0);
const [showCopySuccess, setShowCopySuccess] = useState(false); const [showCopySuccess, setShowCopySuccess] = useState(false);
const [messages, setMessages] = useState<z.infer<typeof CopilotMessage>[]>([]);
const appRef = useRef<{ handleCopyChat: () => void }>(null); const appRef = useRef<{ handleCopyChat: () => void }>(null);
function handleNewChat() { function handleNewChat() {
setCopilotKey(prev => prev + 1); setCopilotKey(prev => prev + 1);
setMessages([]);
} }
function handleCopyJson(data: { messages: any[], lastRequest: any, lastResponse: any }) { function handleCopyJson(data: { messages: any[], lastRequest: any, lastResponse: any }) {
@ -320,6 +329,7 @@ export function Copilot({
return ( return (
<Panel variant="copilot" <Panel variant="copilot"
showWelcome={messages.length === 0}
title={ title={
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="text-sm font-medium text-gray-900 dark:text-gray-100"> <div className="text-sm font-medium text-gray-900 dark:text-gray-100">
@ -364,6 +374,7 @@ export function Copilot({
dispatch={dispatch} dispatch={dispatch}
chatContext={chatContext} chatContext={chatContext}
onCopyJson={handleCopyJson} onCopyJson={handleCopyJson}
onMessagesChange={setMessages}
/> />
</div> </div>
</Panel> </Panel>

View file

@ -1,4 +1,5 @@
import clsx from "clsx"; import clsx from "clsx";
import { Sparkles } from "lucide-react";
export function ActionButton({ export function ActionButton({
icon = null, icon = null,
@ -34,6 +35,7 @@ interface PanelProps {
children: React.ReactNode; children: React.ReactNode;
maxHeight?: string; maxHeight?: string;
variant?: 'default' | 'copilot' | 'projects'; variant?: 'default' | 'copilot' | 'projects';
showWelcome?: boolean;
} }
export function Panel({ export function Panel({
@ -43,17 +45,31 @@ export function Panel({
children, children,
maxHeight, maxHeight,
variant = 'default', variant = 'default',
showWelcome = true,
}: PanelProps) { }: PanelProps) {
return <div className={clsx( return <div className={clsx(
"flex flex-col overflow-hidden rounded-xl border", "flex flex-col overflow-hidden rounded-xl border relative",
"border-zinc-200 dark:border-zinc-800", "border-zinc-200 dark:border-zinc-800",
"bg-white dark:bg-zinc-900", "bg-white dark:bg-zinc-900",
maxHeight ? "max-h-[var(--panel-height)]" : "h-full" maxHeight ? "max-h-[var(--panel-height)]" : "h-full"
)} )}
style={{ '--panel-height': maxHeight } as React.CSSProperties} style={{
'--panel-height': maxHeight
} as React.CSSProperties}
> >
{variant === 'copilot' && showWelcome && (
<div className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none -mt-16">
<Sparkles className="w-32 h-32 text-blue-400/40 dark:text-blue-500/25 animate-sparkle" />
<div className="relative mt-8 max-w-full px-8">
<div className="font-mono text-sm whitespace-nowrap text-blue-400/60 dark:text-blue-500/40 font-small inline-flex">
<div className="overflow-hidden w-0 animate-typing">What can I help you build?</div>
<div className="border-r-2 border-blue-400 dark:border-blue-500 animate-cursor">&nbsp;</div>
</div>
</div>
</div>
)}
<div className={clsx( <div className={clsx(
"shrink-0 border-b border-zinc-100 dark:border-zinc-800", "shrink-0 border-b border-zinc-100 dark:border-zinc-800 relative",
variant === 'projects' ? "flex flex-col gap-3 px-4 py-3" : "flex items-center justify-between px-4 py-3" variant === 'projects' ? "flex flex-col gap-3 px-4 py-3" : "flex items-center justify-between px-4 py-3"
)}> )}>
{variant === 'projects' ? ( {variant === 'projects' ? (

View file

@ -19,11 +19,33 @@ const config: Config = {
'pulse-subtle': { 'pulse-subtle': {
'0%, 100%': { opacity: '1' }, '0%, 100%': { opacity: '1' },
'50%': { opacity: '0.85' } '50%': { opacity: '0.85' }
},
gradient: {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' }
},
'sparkle-fade': {
'0%': { opacity: '0.2', transform: 'scale(0.9)' },
'50%': { opacity: '0.5', transform: 'scale(1.1)' },
'100%': { opacity: '0.2', transform: 'scale(0.9)' }
},
typing: {
'0%, 5%': { width: '0%' },
'45%, 55%': { width: '100%' },
'95%, 100%': { width: '0%' }
},
blink: {
'50%': { borderColor: 'transparent' }
} }
}, },
animation: { animation: {
shine: 'shine 2s infinite', shine: 'shine 2s infinite',
'pulse-subtle': 'pulse-subtle 2s infinite' 'pulse-subtle': 'pulse-subtle 2s infinite',
'gradient': 'gradient var(--gradient-animation-duration, 15s) ease infinite',
'sparkle': 'sparkle-fade 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'typing': 'typing 8s cubic-bezier(0.4, 0, 0.2, 1) infinite',
'cursor': 'blink .75s step-end infinite'
}, },
backgroundImage: { backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',