mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-13 17:22:37 +02:00
Add copilot animations
This commit is contained in:
parent
903ed7ced4
commit
577d637c75
3 changed files with 53 additions and 4 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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"> </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' ? (
|
||||||
|
|
|
||||||
|
|
@ -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))',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue