fixes as fequestd

This commit is contained in:
Stijnus 2025-02-02 18:52:17 +01:00
parent 07435fc255
commit 23d253e7f8
4 changed files with 110 additions and 142 deletions

View File

@ -5,6 +5,12 @@ import { classNames } from '~/utils/classNames';
import { profileStore } from '~/lib/stores/profile'; import { profileStore } from '~/lib/stores/profile';
import type { TabType, Profile } from './types'; import type { TabType, Profile } from './types';
const BetaLabel = () => (
<span className="px-1.5 py-0.5 rounded-full bg-purple-500/10 dark:bg-purple-500/20 text-[10px] font-medium text-purple-600 dark:text-purple-400 ml-2">
BETA
</span>
);
interface AvatarDropdownProps { interface AvatarDropdownProps {
onSelectTab: (tab: TabType) => void; onSelectTab: (tab: TabType) => void;
} }
@ -15,55 +21,52 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => {
return ( return (
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger asChild> <DropdownMenu.Trigger asChild>
<motion.button <motion.button className="group outline-none relative" whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
className="group flex items-center justify-center" {profile?.avatar ? (
whileHover={{ scale: 1.02 }} <div
whileTap={{ scale: 0.98 }} className={classNames(
> 'w-10 h-10',
<div 'rounded-full overflow-hidden',
className={classNames( 'bg-white dark:bg-gray-800',
'w-10 h-10', 'transition-all duration-200',
'rounded-full overflow-hidden', 'relative',
'bg-gray-100/50 dark:bg-gray-800/50', 'shadow-sm',
'flex items-center justify-center', )}
'ring-1 ring-gray-200/50 dark:ring-gray-700/50', >
'group-hover:ring-purple-500/50 dark:group-hover:ring-purple-500/50', <img
'group-hover:bg-purple-500/10 dark:group-hover:bg-purple-500/10', src={profile.avatar}
'transition-all duration-200', alt={profile?.username || 'Profile'}
'relative', className={classNames(
)} 'w-full h-full',
> 'object-cover',
{profile?.avatar ? ( 'transform-gpu',
<div className="w-full h-full"> 'image-rendering-crisp',
<img 'group-hover:brightness-110',
src={profile.avatar} 'group-hover:scale-105',
alt={profile?.username || 'Profile'} 'transition-all duration-200',
className={classNames( )}
'w-full h-full', loading="eager"
'object-cover', decoding="sync"
'transform-gpu', />
'image-rendering-crisp', </div>
'group-hover:brightness-110', ) : (
'group-hover:scale-105', <div
'transition-all duration-200', className={classNames(
)} 'w-10 h-10',
loading="eager" 'rounded-full',
decoding="sync" 'flex items-center justify-center',
/> 'bg-white dark:bg-gray-800',
<div 'text-gray-400 dark:text-gray-500',
className={classNames( 'group-hover:text-purple-500 dark:group-hover:text-purple-400',
'absolute inset-0', 'transition-all duration-200',
'ring-1 ring-inset ring-black/5 dark:ring-white/5', 'shadow-sm',
'group-hover:ring-purple-500/20 dark:group-hover:ring-purple-500/20', )}
'group-hover:bg-purple-500/5 dark:group-hover:bg-purple-500/5', >
'transition-colors duration-200', <div className="i-ph:question w-6 h-6" />
)} </div>
/> )}
</div> {/* Add subtle circular highlight effect */}
) : ( <div className="absolute inset-0 rounded-full bg-gradient-to-b from-white/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="i-ph:robot-fill w-6 h-6 text-gray-400 dark:text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" />
)}
</div>
</motion.button> </motion.button>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
@ -86,7 +89,7 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => {
'border-b border-gray-200/50 dark:border-gray-800/50', 'border-b border-gray-200/50 dark:border-gray-800/50',
)} )}
> >
<div className="w-10 h-10 rounded-full overflow-hidden bg-gray-100/50 dark:bg-gray-800/50 flex-shrink-0"> <div className="w-10 h-10 rounded-full overflow-hidden flex-shrink-0 bg-white dark:bg-gray-800 shadow-sm">
{profile?.avatar ? ( {profile?.avatar ? (
<img <img
src={profile.avatar} src={profile.avatar}
@ -96,8 +99,8 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => {
decoding="sync" decoding="sync"
/> />
) : ( ) : (
<div className="w-full h-full flex items-center justify-center"> <div className="w-full h-full flex items-center justify-center text-gray-400 dark:text-gray-500 font-medium text-lg">
<div className="i-ph:robot-fill w-6 h-6 text-gray-400 dark:text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> <span className="relative -top-0.5">?</span>
</div> </div>
)} )}
</div> </div>
@ -121,7 +124,7 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => {
)} )}
onClick={() => onSelectTab('profile')} onClick={() => onSelectTab('profile')}
> >
<div className="i-ph:robot-fill w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> <div className="i-ph:user-circle w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" />
Edit Profile Edit Profile
</DropdownMenu.Item> </DropdownMenu.Item>
@ -137,7 +140,7 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => {
)} )}
onClick={() => onSelectTab('settings')} onClick={() => onSelectTab('settings')}
> >
<div className="i-ph:gear-six-fill w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> <div className="i-ph:gear-six w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" />
Settings Settings
</DropdownMenu.Item> </DropdownMenu.Item>
@ -155,8 +158,9 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => {
)} )}
onClick={() => onSelectTab('task-manager')} onClick={() => onSelectTab('task-manager')}
> >
<div className="i-ph:activity-fill w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> <div className="i-ph:activity w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" />
Task Manager Task Manager
<BetaLabel />
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
@ -171,8 +175,9 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => {
)} )}
onClick={() => onSelectTab('service-status')} onClick={() => onSelectTab('service-status')}
> >
<div className="i-ph:heartbeat-fill w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> <div className="i-ph:heartbeat w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" />
Service Status Service Status
<BetaLabel />
</DropdownMenu.Item> </DropdownMenu.Item>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Portal> </DropdownMenu.Portal>

View File

@ -82,6 +82,15 @@ const TAB_DESCRIPTIONS: Record<TabType, string> = {
'tab-management': 'Configure visible tabs and their order', 'tab-management': 'Configure visible tabs and their order',
}; };
// Beta status for experimental features
const BETA_TABS = new Set<TabType>(['task-manager', 'service-status']);
const BetaLabel = () => (
<div className="absolute top-2 right-2 px-1.5 py-0.5 rounded-full bg-purple-500/10 dark:bg-purple-500/20">
<span className="text-[10px] font-medium text-purple-600 dark:text-purple-400">BETA</span>
</div>
);
const AnimatedSwitch = ({ checked, onCheckedChange, id, label }: AnimatedSwitchProps) => { const AnimatedSwitch = ({ checked, onCheckedChange, id, label }: AnimatedSwitchProps) => {
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -108,11 +117,12 @@ const AnimatedSwitch = ({ checked, onCheckedChange, id, label }: AnimatedSwitchP
'group-hover:shadow-md group-active:shadow-sm', 'group-hover:shadow-md group-active:shadow-sm',
'group-hover:scale-95 group-active:scale-90', 'group-hover:scale-95 group-active:scale-90',
)} )}
layout initial={false}
transition={{ transition={{
type: 'spring', type: 'spring',
stiffness: 500, stiffness: 500,
damping: 30, damping: 30,
duration: 0.2,
}} }}
animate={{ animate={{
x: checked ? '1.25rem' : '0rem', x: checked ? '1.25rem' : '0rem',
@ -414,29 +424,13 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
{/* Header */} {/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700"> <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
{activeTab || showTabManagement ? ( {(activeTab || showTabManagement) && (
<button <button
onClick={handleBack} onClick={handleBack}
className="flex items-center justify-center w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200" className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
> >
<div className="i-ph:arrow-left w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" /> <div className="i-ph:arrow-left w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
</button> </button>
) : (
<motion.div
className="w-7 h-7"
initial={{ rotate: -5 }}
animate={{ rotate: 5 }}
transition={{
repeat: Infinity,
repeatType: 'reverse',
duration: 2,
ease: 'easeInOut',
}}
>
<div className="w-full h-full flex items-center justify-center bg-gray-100/50 dark:bg-gray-800/50 rounded-full">
<div className="i-ph:lightning-fill w-5 h-5 text-purple-500 dark:text-purple-400 transition-colors" />
</div>
</motion.div>
)} )}
<DialogTitle className="text-xl font-semibold text-gray-900 dark:text-white"> <DialogTitle className="text-xl font-semibold text-gray-900 dark:text-white">
{showTabManagement ? 'Tab Management' : activeTab ? TAB_LABELS[activeTab] : 'Control Panel'} {showTabManagement ? 'Tab Management' : activeTab ? TAB_LABELS[activeTab] : 'Control Panel'}
@ -462,7 +456,7 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
{/* Close Button */} {/* Close Button */}
<button <button
onClick={onClose} onClick={onClose}
className="flex items-center justify-center w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200" className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
> >
<div className="i-ph:x w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" /> <div className="i-ph:x w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
</button> </button>
@ -513,8 +507,10 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
statusMessage={getStatusMessage(tab.id)} statusMessage={getStatusMessage(tab.id)}
description={TAB_DESCRIPTIONS[tab.id]} description={TAB_DESCRIPTIONS[tab.id]}
isLoading={loadingTab === tab.id} isLoading={loadingTab === tab.id}
className="h-full" className="h-full relative"
/> >
{BETA_TABS.has(tab.id) && <BetaLabel />}
</TabTile>
</motion.div> </motion.div>
))} ))}
</AnimatePresence> </AnimatePresence>

View File

@ -13,9 +13,10 @@ interface TabTileProps {
description?: string; description?: string;
isLoading?: boolean; isLoading?: boolean;
className?: string; className?: string;
children?: React.ReactNode;
} }
export const TabTile = ({ export const TabTile: React.FC<TabTileProps> = ({
tab, tab,
onClick, onClick,
isActive, isActive,
@ -24,6 +25,7 @@ export const TabTile = ({
description, description,
isLoading, isLoading,
className, className,
children,
}: TabTileProps) => { }: TabTileProps) => {
return ( return (
<Tooltip.Provider delayDuration={200}> <Tooltip.Provider delayDuration={200}>
@ -100,62 +102,33 @@ export const TabTile = ({
</div> </div>
</div> </div>
{/* Status Indicator */} {/* Update Indicator with Tooltip */}
{hasUpdate && ( {hasUpdate && (
<motion.div <>
className={classNames( <div className="absolute top-4 right-4 w-2 h-2 rounded-full bg-purple-500 dark:bg-purple-400 animate-pulse" />
'absolute top-3 right-3', <Tooltip.Portal>
'w-2.5 h-2.5 rounded-full', <Tooltip.Content
'bg-purple-500', className={classNames(
'ring-4 ring-purple-500', 'px-3 py-1.5 rounded-lg',
)} 'bg-[#18181B] text-white',
initial={{ scale: 0 }} 'text-sm font-medium',
animate={{ scale: 1 }} 'select-none',
transition={{ type: 'spring', bounce: 0.5 }} 'z-[100]',
/> )}
side="top"
sideOffset={5}
>
{statusMessage}
<Tooltip.Arrow className="fill-[#18181B]" />
</Tooltip.Content>
</Tooltip.Portal>
</>
)} )}
{/* Loading Overlay */} {/* Children (e.g. Beta Label) */}
{isLoading && ( {children}
<motion.div
className={classNames(
'absolute inset-0 rounded-xl z-10',
'bg-white dark:bg-black',
'flex items-center justify-center',
)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}
>
<motion.div
className={classNames('w-8 h-8 rounded-full', 'border-2 border-purple-500', 'border-t-purple-500')}
animate={{ rotate: 360 }}
transition={{
duration: 1,
repeat: Infinity,
ease: 'linear',
}}
/>
</motion.div>
)}
</motion.div> </motion.div>
</Tooltip.Trigger> </Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className={classNames(
'px-3 py-1.5 rounded-lg',
'bg-[#18181B] text-white',
'text-sm font-medium',
'select-none',
'z-[100]',
)}
side="top"
sideOffset={5}
>
{statusMessage || TAB_LABELS[tab.id]}
<Tooltip.Arrow className="fill-[#18181B]" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root> </Tooltip.Root>
</Tooltip.Provider> </Tooltip.Provider>
); );

View File

@ -121,19 +121,13 @@ export default function FeaturesTab() {
// Enable features by default on first load // Enable features by default on first load
React.useEffect(() => { React.useEffect(() => {
// Only enable if they haven't been explicitly set before // Force enable these features by default
if (isLatestBranch === undefined) { enableLatestBranch(true);
enableLatestBranch(true); enableContextOptimization(true);
} setAutoSelectTemplate(true);
setPromptId('optimized');
if (contextOptimizationEnabled === undefined) {
enableContextOptimization(true);
}
if (autoSelectTemplate === undefined) {
setAutoSelectTemplate(true);
}
// Only enable event logs if not explicitly set before
if (eventLogs === undefined) { if (eventLogs === undefined) {
setEventLogs(true); setEventLogs(true);
} }