Mega UI revamp

This commit is contained in:
akhisud3195 2025-03-27 18:52:17 +05:30 committed by Ramnique Singh
parent 650f481a96
commit bcb686a20d
94 changed files with 6984 additions and 3889 deletions

View file

@ -0,0 +1,145 @@
'use client';
import { Button, Spinner } from "@heroui/react";
import { useRef, useState, useEffect } from "react";
import { Textarea } from "@/components/ui/textarea";
// Add a type to support both message formats
type FlexibleMessage = {
role: 'user' | 'assistant' | 'system' | 'tool';
content: string | any;
version?: string;
chatId?: string;
createdAt?: string;
// Add any other optional fields that might be needed
};
export function ComposeBox({
minRows=3,
disabled=false,
loading=false,
handleUserMessage,
messages,
}: {
minRows?: number;
disabled?: boolean;
loading?: boolean;
handleUserMessage: (prompt: string) => void;
messages: FlexibleMessage[]; // Use the flexible message type
}) {
const [input, setInput] = useState('');
const [isFocused, setIsFocused] = useState(false);
const inputRef = useRef<HTMLTextAreaElement>(null);
function handleInput() {
const prompt = input.trim();
if (!prompt) {
return;
}
setInput('');
handleUserMessage(prompt);
}
function handleInputKeyDown(event: React.KeyboardEvent<HTMLTextAreaElement>) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
handleInput();
}
}
// focus on the input field
useEffect(() => {
inputRef.current?.focus();
}, [messages]);
return (
<div className="relative group">
{/* Keyboard shortcut hint */}
<div className="absolute -top-6 right-0 text-xs text-gray-500 dark:text-gray-400 opacity-0
group-hover:opacity-100 transition-opacity">
Press + Enter to send
</div>
{/* Outer container with padding */}
<div className="rounded-2xl border-[1.5px] border-gray-200 dark:border-[#2a2d31] p-3 relative
bg-white dark:bg-[#1e2023] flex items-end gap-2">
{/* Textarea */}
<div className="flex-1">
<Textarea
ref={inputRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleInputKeyDown}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
disabled={disabled || loading}
placeholder="Type a message..."
autoResize={true}
maxHeight={120}
className={`
!min-h-0
!border-0 !shadow-none !ring-0
bg-transparent
resize-none
overflow-y-auto
[&::-webkit-scrollbar]:w-1
[&::-webkit-scrollbar-track]:bg-transparent
[&::-webkit-scrollbar-thumb]:bg-gray-300
[&::-webkit-scrollbar-thumb]:dark:bg-[#2a2d31]
[&::-webkit-scrollbar-thumb]:rounded-full
placeholder:text-gray-500 dark:placeholder:text-gray-400
`}
/>
</div>
{/* Send button */}
<Button
size="sm"
isIconOnly
disabled={disabled || loading || !input.trim()}
onPress={handleInput}
className={`
transition-all duration-200
${input.trim()
? 'bg-indigo-50 hover:bg-indigo-100 text-indigo-700 dark:bg-indigo-900/50 dark:hover:bg-indigo-800/60 dark:text-indigo-300'
: 'bg-gray-100 dark:bg-gray-800 text-gray-400 dark:text-gray-500'
}
scale-100 hover:scale-105 active:scale-95
disabled:opacity-50 disabled:scale-95
hover:shadow-md dark:hover:shadow-indigo-950/10
mb-0.5
`}
>
{loading ? (
<Spinner size="sm" color={input.trim() ? "primary" : "default"} />
) : (
<SendIcon
size={16}
className={`transform transition-transform ${isFocused ? 'translate-x-0.5' : ''}`}
/>
)}
</Button>
</div>
</div>
);
}
// Custom SendIcon component for better visual alignment
function SendIcon({ size, className }: { size: number, className?: string }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M22 2L11 13" />
<path d="M22 2L15 22L11 13L2 9L22 2Z" />
</svg>
);
}

View file

@ -0,0 +1,11 @@
import { CopyButton } from "@/components/common/copy-button";
export function CopyAsJsonButton({ onCopy }: { onCopy: () => void }) {
return <div className="absolute top-0 right-0">
<CopyButton
onCopy={onCopy}
label="Copy as JSON"
successLabel="Copied"
/>
</div>
}

View file

@ -0,0 +1,41 @@
'use client';
import { Button } from "@/components/ui/button";
import { CopyIcon, CheckIcon } from "lucide-react";
import { useState } from "react";
export function CopyButton({
onCopy,
label,
successLabel,
}: {
onCopy: () => void;
label: string;
successLabel: string;
}) {
const [showCopySuccess, setShowCopySuccess] = useState(false);
const handleCopy = () => {
onCopy();
setShowCopySuccess(true);
setTimeout(() => {
setShowCopySuccess(false);
}, 500);
}
return (
<Button
variant="secondary"
size="sm"
onClick={handleCopy}
className="gap-2"
showHoverContent
hoverContent={showCopySuccess ? successLabel : label}
>
{showCopySuccess ? (
<CheckIcon className="h-4 w-4" />
) : (
<CopyIcon className="h-4 w-4" />
)}
</Button>
);
}

View file

@ -0,0 +1,93 @@
import clsx from "clsx";
export function ActionButton({
icon = null,
children,
onClick = undefined,
disabled = false,
primary = false,
}: {
icon?: React.ReactNode;
children: React.ReactNode;
onClick?: () => void | undefined;
disabled?: boolean;
primary?: boolean;
}) {
const onClickProp = onClick ? { onClick } : {};
return <button
disabled={disabled}
className={clsx("rounded-md text-xs flex items-center gap-1 disabled:text-gray-300 dark:disabled:text-gray-600 hover:text-gray-600 dark:hover:text-gray-300", {
"text-blue-600 dark:text-blue-400": primary,
"text-gray-400 dark:text-gray-500": !primary,
})}
{...onClickProp}
>
{icon}
{children}
</button>;
}
interface PanelProps {
title: React.ReactNode;
rightActions?: React.ReactNode;
actions?: React.ReactNode;
children: React.ReactNode;
maxHeight?: string;
variant?: 'default' | 'copilot' | 'projects';
}
export function Panel({
title,
rightActions,
actions,
children,
maxHeight,
variant = 'default',
}: PanelProps) {
return <div className={clsx(
"flex flex-col overflow-hidden rounded-xl border",
"border-zinc-200 dark:border-zinc-800",
"bg-white dark:bg-zinc-900",
maxHeight ? "max-h-[var(--panel-height)]" : "h-full"
)}
style={{ '--panel-height': maxHeight } as React.CSSProperties}
>
<div className={clsx(
"shrink-0 border-b border-zinc-100 dark:border-zinc-800",
variant === 'projects' ? "flex flex-col gap-3 px-4 py-3" : "flex items-center justify-between px-4 py-3"
)}>
{variant === 'projects' ? (
<>
<div className="text-sm uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
{title}
</div>
{actions && <div className="flex items-center gap-2">
{actions}
</div>}
</>
) : variant === 'copilot' ? (
<>
<div className="flex items-center gap-2">
{title}
</div>
{rightActions}
</>
) : (
<>
{title}
{rightActions}
</>
)}
</div>
<div className={clsx(
"min-h-0 flex-1 overflow-y-auto",
variant === 'projects' && "custom-scrollbar"
)}>
{variant === 'projects' ? (
<div className="px-3 py-2 pb-4">
{children}
</div>
) : children}
</div>
</div>;
}