mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
✨ feat: Artifact Management Enhancements, Version Control, and UI Refinements (#10318)
* ✨ feat: Enhance Artifact Management with Version Control and UI Improvements ✨ feat: Improve mobile layout and responsiveness in Artifacts component ✨ feat: Refactor imports and remove unnecessary props in Artifact components ✨ feat: Enhance Artifacts and SidePanel components with improved mobile responsiveness and layout transitions feat: Enhance artifact panel animations and improve UI responsiveness - Updated Thinking component button styles for smoother transitions. - Implemented dynamic rendering for artifacts panel with animation effects. - Refactored localization keys for consistency across multiple languages. - Added new CSS animations for iOS-inspired smooth transitions. - Improved Tailwind CSS configuration to support enhanced animation effects. ✨ feat: Add fullWidth and icon support to Radio component for enhanced flexibility refactor: Remove unused PreviewProps import in ArtifactPreview component refactor: Improve button class handling and blur effect constants in Artifact components ✨ feat: Refactor Artifacts component structure and add mobile/desktop variants for improved UI chore: Bump @librechat/client version to 0.3.2 refactor: Update button styles and transition durations for improved UI responsiveness refactor: revert back localization key refactor: remove unused scaling and animation properties for cleaner CSS refactor: remove unused animation properties for cleaner configuration * ✨ refactor: Simplify className usage in ArtifactTabs, ArtifactsHeader, and SidePanelGroup components * refactor: Remove cycleArtifact function from useArtifacts hook * ✨ feat: Implement Chromium resize lag fix with performance optimizations and new ArtifactsPanel component * ✨ feat: Update Badge component for responsive design and improve tap scaling behavior * chore: Update react-resizable-panels dependency to version 3.0.6 * ✨ feat: Refactor Artifacts components for improved structure and performance; remove unused files and optimize styles * ✨ style: Update text color for improved visibility in Artifacts component * ✨ style: Remove text color class for improved Spinner styling in Artifacts component * refactor: Split EditorContext into MutationContext and CodeContext to optimize re-renders; update related components to use new hooks * refactor: Optimize debounced mutation handling in CodeEditor component using refs to maintain current values and reduce re-renders * fix: Correct endpoint for message artifacts by changing URL segment from 'artifacts' to 'artifact' * feat: Enhance useEditArtifact mutation with optimistic updates and rollback on error; improve type safety with context management * fix: proper switch to preview as soon as artifact becomes enclosed * refactor: Remove optimistic updates from useEditArtifact mutation to prevent errors; simplify onMutate logic * test: Add comprehensive unit tests for useArtifacts hook to validate artifact handling, tab switching, and state management * test: Enhance unit tests for useArtifacts hook to cover new conversation transitions and null message handling --------- Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>
This commit is contained in:
parent
4186db3ce2
commit
b8b1217c34
25 changed files with 1565 additions and 345 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@librechat/client",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.2",
|
||||
"description": "React components for LibreChat",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.es.js",
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
"react-dom": "^18.2.0 || ^19.1.0",
|
||||
"react-hook-form": "^7.56.4",
|
||||
"react-i18next": "^15.4.0 || ^15.6.0",
|
||||
"react-resizable-panels": "^3.0.2",
|
||||
"react-resizable-panels": "^3.0.6",
|
||||
"react-textarea-autosize": "^8.4.0",
|
||||
"tailwind-merge": "^1.9.1"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -53,13 +53,23 @@ export default function Badge({
|
|||
}
|
||||
};
|
||||
|
||||
const getWhileTapScale = () => {
|
||||
if (isDragging) {
|
||||
return 1.1;
|
||||
}
|
||||
if (isDisabled) {
|
||||
return 1;
|
||||
}
|
||||
return 0.97;
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
onClick={handleClick}
|
||||
className={cn(
|
||||
'group relative inline-flex items-center gap-1.5 rounded-full px-4 py-1.5',
|
||||
'border border-border-medium text-sm font-medium transition-shadow md:w-full',
|
||||
'size-9 p-2 md:p-3',
|
||||
'border border-border-medium text-sm font-medium transition-shadow',
|
||||
'@container-[600px]:w-full size-9 p-2',
|
||||
isActive
|
||||
? 'bg-surface-active shadow-md'
|
||||
: 'bg-surface-chat shadow-sm hover:bg-surface-hover hover:shadow-md',
|
||||
|
|
@ -72,16 +82,23 @@ export default function Badge({
|
|||
scale: isDragging ? 1.1 : 1,
|
||||
boxShadow: isDragging ? '0 10px 25px rgba(0,0,0,0.1)' : undefined,
|
||||
}}
|
||||
whileTap={{ scale: isDragging ? 1.1 : isDisabled ? 1 : 0.97 }}
|
||||
whileTap={{ scale: getWhileTapScale() }}
|
||||
transition={{ type: 'tween', duration: 0.1, ease: 'easeOut' }}
|
||||
{...(props as React.ComponentProps<typeof motion.button>)}
|
||||
>
|
||||
{Icon && <Icon className={cn('relative h-5 w-5 md:h-4 md:w-4', !label && 'mx-auto')} />}
|
||||
<span className="relative hidden md:inline">{label}</span>
|
||||
{Icon && (
|
||||
<Icon
|
||||
className={cn(
|
||||
'@container-[600px]:h-4 @container-[600px]:w-4 relative h-5 w-5',
|
||||
!label && 'mx-auto',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<span className="@container-[600px]:inline relative hidden">{label}</span>
|
||||
|
||||
{isEditing && !isDragging && (
|
||||
<motion.button
|
||||
className="absolute -right-1 -top-1 flex h-6 w-6 items-center justify-center rounded-full bg-surface-secondary-alt text-text-primary shadow-sm md:h-5 md:w-5"
|
||||
className="@container-[600px]:h-5 @container-[600px]:w-5 absolute -right-1 -top-1 flex h-6 w-6 items-center justify-center rounded-full bg-surface-secondary-alt text-text-primary shadow-sm"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useLocalize } from '~/hooks';
|
|||
interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface RadioProps {
|
||||
|
|
@ -11,9 +12,18 @@ interface RadioProps {
|
|||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
const Radio = memo(function Radio({ options, value, onChange, disabled = false }: RadioProps) {
|
||||
const Radio = memo(function Radio({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
className = '',
|
||||
fullWidth = false,
|
||||
}: RadioProps) {
|
||||
const localize = useLocalize();
|
||||
const [currentValue, setCurrentValue] = useState<string>(value ?? '');
|
||||
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
||||
|
|
@ -67,7 +77,10 @@ const Radio = memo(function Radio({ options, value, onChange, disabled = false }
|
|||
const selectedIndex = options.findIndex((opt) => opt.value === currentValue);
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex items-center rounded-lg bg-muted p-1" role="radiogroup">
|
||||
<div
|
||||
className={`relative ${fullWidth ? 'flex' : 'inline-flex'} items-center rounded-lg bg-muted p-1 ${className}`}
|
||||
role="radiogroup"
|
||||
>
|
||||
{selectedIndex >= 0 && (
|
||||
<div
|
||||
className="pointer-events-none absolute inset-y-1 rounded-md border border-border/50 bg-background shadow-sm transition-all duration-300 ease-out"
|
||||
|
|
@ -85,10 +98,11 @@ const Radio = memo(function Radio({ options, value, onChange, disabled = false }
|
|||
aria-checked={currentValue === option.value}
|
||||
onClick={() => handleChange(option.value)}
|
||||
disabled={disabled}
|
||||
className={`relative z-10 flex h-[34px] items-center justify-center rounded-md px-4 text-sm font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
||||
className={`relative z-10 flex h-[34px] items-center justify-center gap-2 rounded-md px-4 text-sm font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
||||
currentValue === option.value ? 'text-foreground' : 'text-foreground'
|
||||
} ${disabled ? 'cursor-not-allowed opacity-50' : ''}`}
|
||||
} ${disabled ? 'cursor-not-allowed opacity-50' : ''} ${fullWidth ? 'flex-1' : ''}`}
|
||||
>
|
||||
{option.icon && <span className="flex-shrink-0">{option.icon}</span>}
|
||||
<span className="whitespace-nowrap">{option.label}</span>
|
||||
</button>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ const ResizableHandleAlt = ({
|
|||
{...props}
|
||||
>
|
||||
{withHandle && (
|
||||
<div className="invisible z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border group-hover:visible group-active:visible">
|
||||
<div className="invisible z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border group-hover:visible group-active:visible group-data-[resize-handle-active]:visible">
|
||||
<GripVertical className="h-2.5 w-2.5" />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue