mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 06:52:47 +00:00
refactor(client): migrate dialogs, toggles and remaining controls to @librechat/client
Swap behavioral controls for @librechat/client equivalents: HeadlessUI and legacy dialogs to OGDialog, native checkbox/switch to Checkbox/Switch (onCheckedChange), and remaining buttons/inputs across Chat, Skills, Tools, Sharing, Memories and Settings. Convert applicable native title= tooltips to TooltipAnchor and localize close/scroll aria-labels. Skip swaps that would break floating-label animations or bespoke select behavior. Update co-located test mocks to provide the newly-used Button and cn dependencies.
This commit is contained in:
parent
9317bf6785
commit
b235ad2cbf
30 changed files with 356 additions and 320 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { memo, useCallback, useRef } from 'react';
|
||||
import { MicOff } from 'lucide-react';
|
||||
import { useToastContext, TooltipAnchor, ListeningIcon, Spinner } from '@librechat/client';
|
||||
import { Button, useToastContext, TooltipAnchor, ListeningIcon, Spinner } from '@librechat/client';
|
||||
import { useLocalize, useSpeechToText, useGetAudioSettings } from '~/hooks';
|
||||
import { globalAudioId, type TAskFunction } from '~/common';
|
||||
import { useChatFormContext } from '~/Providers';
|
||||
|
|
@ -107,20 +107,19 @@ export default memo(function AudioRecorder({
|
|||
<TooltipAnchor
|
||||
description={localize('com_ui_use_micrphone')}
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
id="audio-recorder"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label={localize('com_ui_use_micrphone')}
|
||||
onClick={isListening === true ? handleStopRecording : handleStartRecording}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'flex size-9 items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover',
|
||||
)}
|
||||
title={localize('com_ui_use_micrphone')}
|
||||
className={cn('size-9 rounded-full p-1')}
|
||||
aria-pressed={isListening}
|
||||
>
|
||||
{renderIcon()}
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { memo, useState, useRef, useEffect } from 'react';
|
||||
import { AutoSizer, List } from 'react-virtualized';
|
||||
import { Spinner, useCombobox } from '@librechat/client';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { Input, Spinner, useCombobox } from '@librechat/client';
|
||||
import type { RecoilState } from 'recoil';
|
||||
import type { MentionOption, ConvoGenerator } from '~/common';
|
||||
import { useGetConversation, useLocalize, TranslationKeys } from '~/hooks';
|
||||
|
|
@ -182,10 +182,10 @@ function MentionContent({
|
|||
return (
|
||||
<div className="absolute bottom-28 z-10 w-full space-y-2">
|
||||
<div className="popover border-token-border-light rounded-2xl border bg-surface-secondary p-2 shadow-lg">
|
||||
<input
|
||||
<Input
|
||||
ref={initInputRef}
|
||||
placeholder={localize(placeholder)}
|
||||
className="mb-1 w-full border-0 bg-surface-secondary p-2 text-sm text-text-primary focus:outline-none"
|
||||
className="mb-1 h-auto w-full rounded-none border-0 bg-surface-secondary p-2 text-sm text-text-primary focus:outline-none"
|
||||
autoComplete="off"
|
||||
value={searchValue}
|
||||
onKeyDown={(e) => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { getEndpointField } from 'librechat-data-provider';
|
|||
import {
|
||||
Dialog,
|
||||
Label,
|
||||
Button,
|
||||
PinIcon,
|
||||
EditIcon,
|
||||
TrashIcon,
|
||||
|
|
@ -51,17 +52,18 @@ const PresetItems: FC<{
|
|||
tabIndex={-1}
|
||||
>
|
||||
<div className="flex h-full grow items-center justify-end gap-2">
|
||||
<label
|
||||
<Label
|
||||
htmlFor="default-preset"
|
||||
className="w-40 truncate rounded bg-transparent py-1 text-xs font-medium text-text-secondary transition-colors sm:w-72"
|
||||
>
|
||||
{defaultPreset
|
||||
? `${localize('com_endpoint_preset_default_item')} ${defaultPreset.title}`
|
||||
: localize('com_endpoint_preset_default_none')}
|
||||
</label>
|
||||
</Label>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
type="button"
|
||||
className="mr-1 flex h-[32px] cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-medium text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-destructive focus:ring-text-primary"
|
||||
aria-label={localize('com_ui_clear_all')}
|
||||
|
|
@ -78,7 +80,7 @@ const PresetItems: FC<{
|
|||
<path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0M9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1M6.854 7.146 8 8.293l1.146-1.147a.5.5 0 1 1 .708.708L8.707 9l1.147 1.146a.5.5 0 0 1-.708.708L8 9.707l-1.146 1.147a.5.5 0 0 1-.708-.708L7.293 9 6.146 7.854a.5.5 0 1 1 .708-.708"></path>
|
||||
</svg>
|
||||
{localize('com_ui_clear_all')}
|
||||
</button>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogTemplate
|
||||
showCloseButton={false}
|
||||
|
|
@ -173,7 +175,8 @@ const PresetItems: FC<{
|
|||
: localize('com_ui_pin')
|
||||
}
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'm-0 h-full rounded-md bg-transparent p-2 text-text-tertiary hover:text-text-primary focus:text-text-primary',
|
||||
defaultPreset?.presetId === presetId
|
||||
|
|
@ -194,14 +197,15 @@ const PresetItems: FC<{
|
|||
}}
|
||||
>
|
||||
<PinIcon unpin={defaultPreset?.presetId === presetId} />
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_edit')}
|
||||
aria-label={localize('com_ui_edit')}
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="m-0 h-full rounded-md p-2 text-text-tertiary hover:text-text-primary focus:text-text-primary sm:invisible sm:group-focus-within:visible sm:group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -217,14 +221,15 @@ const PresetItems: FC<{
|
|||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_delete')}
|
||||
aria-label={localize('com_ui_delete')}
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="m-0 h-full rounded-md p-2 text-text-tertiary hover:text-text-primary focus:text-text-primary sm:invisible sm:group-focus-within:visible sm:group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -240,7 +245,7 @@ const PresetItems: FC<{
|
|||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import { useCallback, useEffect, useId, useMemo, useReducer, useRef, useState }
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { ContentTypes, EModelEndpoint } from 'librechat-data-provider';
|
||||
import { ArrowDown, ChevronRight, Maximize2, Minimize2, Users } from 'lucide-react';
|
||||
import { OGDialog, OGDialogContent, OGDialogTitle, OGDialogDescription } from '@librechat/client';
|
||||
import {
|
||||
Button,
|
||||
OGDialog,
|
||||
OGDialogTitle,
|
||||
OGDialogContent,
|
||||
OGDialogDescription,
|
||||
} from '@librechat/client';
|
||||
import type { Agents, TAttachment, TMessage, TMessageContentParts } from 'librechat-data-provider';
|
||||
import type { PartWithIndex } from '~/components/Chat/Messages/Content/ParallelContent';
|
||||
import type { SubagentTickerLine } from '~/utils/subagentContent';
|
||||
|
|
@ -539,14 +545,15 @@ export default function SubagentCall({
|
|||
|
||||
<div className="relative min-h-0 flex-1 border-t border-border-light bg-surface-primary">
|
||||
{!isAtBottom && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={scrollDialogToBottom}
|
||||
aria-label={localize('com_ui_subagent_scroll_to_bottom')}
|
||||
className="absolute bottom-3 right-4 z-10 flex h-8 w-8 items-center justify-center rounded-full border border-border-light bg-surface-secondary text-text-secondary shadow-md transition hover:bg-surface-tertiary hover:text-text-primary"
|
||||
className="absolute bottom-3 right-4 z-10 h-8 w-8 rounded-full border border-border-light bg-surface-secondary text-text-secondary shadow-md transition hover:bg-surface-tertiary hover:text-text-primary"
|
||||
>
|
||||
<ArrowDown size={16} aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
|
|
@ -633,14 +640,15 @@ function SubagentPrompt({
|
|||
<h3 id={headingId} className="text-sm font-medium text-text-primary">
|
||||
{localize('com_ui_prompt')}
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onToggle}
|
||||
aria-controls={contentId}
|
||||
aria-expanded={expanded}
|
||||
aria-label={toggleLabel}
|
||||
title={toggleLabel}
|
||||
className="inline-flex h-8 items-center gap-1.5 rounded-md px-2 text-xs font-medium text-text-secondary transition hover:bg-surface-tertiary hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
className="h-8 gap-1.5 rounded-md px-2 text-xs font-medium text-text-secondary transition hover:bg-surface-tertiary hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
>
|
||||
{expanded ? (
|
||||
<Minimize2 size={14} aria-hidden="true" />
|
||||
|
|
@ -648,7 +656,7 @@ function SubagentPrompt({
|
|||
<Maximize2 size={14} aria-hidden="true" />
|
||||
)}
|
||||
<span className="hidden sm:inline">{toggleLabel}</span>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
id={contentId}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { memo, useMemo, useState, useCallback, useRef, useId, useEffect } from 'react';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Clipboard, CheckMark, TooltipAnchor } from '@librechat/client';
|
||||
import { ScrollText, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import type { MouseEvent, FocusEvent } from 'react';
|
||||
import { Button, Clipboard, CheckMark, TooltipAnchor } from '@librechat/client';
|
||||
import type { SummaryContentPart } from 'librechat-data-provider';
|
||||
import type { MouseEvent, FocusEvent } from 'react';
|
||||
import { fontSizeAtom } from '~/store/fontSize';
|
||||
import { useMessageContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
|
@ -75,13 +75,13 @@ const SummaryButton = memo(
|
|||
|
||||
return (
|
||||
<div className="group/summary flex w-full items-center justify-between gap-2">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onClick}
|
||||
aria-expanded={isExpanded}
|
||||
aria-controls={contentId}
|
||||
className={cn(
|
||||
'group/button flex flex-1 items-center justify-start rounded-lg leading-[18px]',
|
||||
'group/button h-auto flex-1 justify-start gap-0 rounded-lg p-0 font-normal leading-[18px] hover:bg-transparent',
|
||||
fontSize,
|
||||
)}
|
||||
>
|
||||
|
|
@ -99,16 +99,17 @@ const SummaryButton = memo(
|
|||
/>
|
||||
</span>
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
</Button>
|
||||
{content && showCopyButton && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onCopy}
|
||||
aria-label={
|
||||
isCopied ? localize('com_ui_copied_to_clipboard') : localize('com_ui_copy_summary')
|
||||
}
|
||||
className={cn(
|
||||
'rounded-lg p-1.5 text-text-secondary-alt',
|
||||
'size-auto rounded-lg p-1.5 text-text-secondary-alt',
|
||||
isExpanded
|
||||
? 'opacity-0 group-focus-within/summary-container:opacity-100 group-hover/summary-container:opacity-100'
|
||||
: 'opacity-0',
|
||||
|
|
@ -124,7 +125,7 @@ const SummaryButton = memo(
|
|||
) : (
|
||||
<Clipboard size="19" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useMemo, memo, useCallback, useRef, useId, type MouseEvent } from 'react';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Lightbulb, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { Clipboard, CheckMark, TooltipAnchor } from '@librechat/client';
|
||||
import { Button, Clipboard, CheckMark, TooltipAnchor } from '@librechat/client';
|
||||
import type { FocusEvent, FC } from 'react';
|
||||
import { useLocalize, useExpandCollapse } from '~/hooks';
|
||||
import { showThinkingAtom } from '~/store/showThinking';
|
||||
|
|
@ -90,8 +90,9 @@ export const ThinkingButton = memo(
|
|||
{label}
|
||||
</button>
|
||||
{content && showCopyButton && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleCopy}
|
||||
aria-label={
|
||||
isCopied
|
||||
|
|
@ -99,7 +100,7 @@ export const ThinkingButton = memo(
|
|||
: localize('com_ui_copy_thoughts_to_clipboard')
|
||||
}
|
||||
className={cn(
|
||||
'rounded-lg p-1.5 text-text-secondary-alt',
|
||||
'size-auto gap-0 rounded-lg p-1.5 text-text-secondary-alt',
|
||||
isExpanded
|
||||
? 'opacity-0 group-focus-within/thinking-container:opacity-100 group-hover/thinking-container:opacity-100'
|
||||
: 'opacity-0',
|
||||
|
|
@ -117,7 +118,7 @@ export const ThinkingButton = memo(
|
|||
) : (
|
||||
<Clipboard size="19" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -84,6 +84,14 @@ jest.mock('../Attachment', () => ({
|
|||
|
||||
jest.mock('@librechat/client', () => ({
|
||||
__esModule: true,
|
||||
Button: ({
|
||||
children,
|
||||
variant: _variant,
|
||||
size: _size,
|
||||
...props
|
||||
}: React.ComponentProps<'button'> & { variant?: string; size?: string }) => (
|
||||
<button {...props}>{children}</button>
|
||||
),
|
||||
OGDialog: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
OGDialogContent: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="dialog-content">{children}</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ChevronDown } from 'lucide-react';
|
||||
import { Button } from '@librechat/client';
|
||||
import * as Popover from '@radix-ui/react-popover';
|
||||
import CancelledIcon from './CancelledIcon';
|
||||
import { cn } from '~/utils';
|
||||
|
|
@ -84,12 +85,13 @@ export default function ProgressText({
|
|||
|
||||
return (
|
||||
<Wrapper popover={popover}>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'inline-flex w-full items-center gap-2',
|
||||
'h-auto w-full justify-start gap-2 rounded-none p-0 hover:bg-transparent hover:text-inherit disabled:opacity-100',
|
||||
hasInput
|
||||
? 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy'
|
||||
? 'focus-visible:ring-border-heavy focus-visible:ring-offset-0'
|
||||
: 'pointer-events-none',
|
||||
)}
|
||||
disabled={!hasInput}
|
||||
|
|
@ -110,7 +112,7 @@ export default function ProgressText({
|
|||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useRef, useMemo, useEffect, useCallback } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Button } from '@librechat/client';
|
||||
import { ChevronDown, Users } from 'lucide-react';
|
||||
import { Tools, Constants, ContentTypes, ToolCallTypes } from 'librechat-data-provider';
|
||||
import type {
|
||||
|
|
@ -10,12 +11,12 @@ import type {
|
|||
} from 'librechat-data-provider';
|
||||
import type { PartWithIndex } from './ParallelContent';
|
||||
import { useLocalize, useExpandCollapse, scheduleMessageContentLayoutReconcile } from '~/hooks';
|
||||
import { isBashProgrammaticToolCall } from './routing';
|
||||
import { cn, getToolDisplayLabel } from '~/utils';
|
||||
import { StackedToolIcons } from './ToolOutput';
|
||||
import { useMCPIconMap } from '~/hooks/MCP';
|
||||
import { AttachmentGroup } from './Parts';
|
||||
import store from '~/store';
|
||||
import { isBashProgrammaticToolCall } from './routing';
|
||||
|
||||
interface ToolMeta {
|
||||
name: string;
|
||||
|
|
@ -239,9 +240,10 @@ export default function ToolCallGroup({
|
|||
|
||||
return (
|
||||
<div className="mb-2 mt-1" ref={rootRef}>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
type="button"
|
||||
className="inline-flex w-full items-center gap-2 py-1 text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy"
|
||||
className="inline-flex h-auto w-full items-center justify-start gap-2 rounded-none bg-transparent p-0 py-1 text-text-secondary hover:bg-transparent hover:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy focus-visible:ring-offset-0"
|
||||
onClick={handleToggle}
|
||||
aria-expanded={isExpanded}
|
||||
aria-label={groupLabel}
|
||||
|
|
@ -282,7 +284,7 @@ export default function ToolCallGroup({
|
|||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
<div style={expandStyle} onTransitionEnd={handleTransitionEnd} aria-hidden={!isExpanded}>
|
||||
{shouldRenderBody && (
|
||||
<div className="overflow-hidden" ref={expandRef}>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Button } from '@librechat/client';
|
||||
import { UIResourceRenderer } from '@mcp-ui/client';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import type { UIResource } from 'librechat-data-provider';
|
||||
import { useOptionalMessagesOperations } from '~/Providers';
|
||||
import { handleUIAction } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface UIResourceCarouselProps {
|
||||
uiResources: UIResource[];
|
||||
}
|
||||
|
||||
const UIResourceCarousel: React.FC<UIResourceCarouselProps> = React.memo(({ uiResources }) => {
|
||||
const localize = useLocalize();
|
||||
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
||||
const [showRightArrow, setShowRightArrow] = useState(true);
|
||||
const [isContainerHovered, setIsContainerHovered] = useState(false);
|
||||
|
|
@ -70,23 +74,17 @@ const UIResourceCarousel: React.FC<UIResourceCarouselProps> = React.memo(({ uiRe
|
|||
/>
|
||||
|
||||
{showLeftArrow && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => scroll('left')}
|
||||
className={`absolute left-2 top-1/2 z-20 -translate-y-1/2 rounded-xl bg-surface-fixed p-2 text-text-fixed shadow-lg transition-all duration-200 hover:scale-110 hover:bg-surface-fixed-hover hover:shadow-xl active:scale-95 ${
|
||||
className={`absolute left-2 top-1/2 z-20 h-auto w-auto -translate-y-1/2 rounded-xl bg-surface-fixed p-2 text-text-fixed shadow-lg transition-all duration-200 hover:scale-110 hover:bg-surface-fixed-hover hover:text-text-fixed hover:shadow-xl active:scale-95 ${
|
||||
isContainerHovered ? 'opacity-100' : 'pointer-events-none opacity-0'
|
||||
}`}
|
||||
aria-label="Scroll left"
|
||||
aria-label={localize('com_ui_scroll_left')}
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div
|
||||
|
|
@ -122,18 +120,17 @@ const UIResourceCarousel: React.FC<UIResourceCarouselProps> = React.memo(({ uiRe
|
|||
</div>
|
||||
|
||||
{showRightArrow && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => scroll('right')}
|
||||
className={`absolute right-2 top-1/2 z-20 -translate-y-1/2 rounded-xl bg-surface-fixed p-2 text-text-fixed shadow-lg transition-all duration-200 hover:scale-110 hover:bg-surface-fixed-hover hover:shadow-xl active:scale-95 ${
|
||||
className={`absolute right-2 top-1/2 z-20 h-auto w-auto -translate-y-1/2 rounded-xl bg-surface-fixed p-2 text-text-fixed shadow-lg transition-all duration-200 hover:scale-110 hover:bg-surface-fixed-hover hover:text-text-fixed hover:shadow-xl active:scale-95 ${
|
||||
isContainerHovered ? 'opacity-100' : 'pointer-events-none opacity-0'
|
||||
}`}
|
||||
aria-label="Scroll right"
|
||||
aria-label={localize('com_ui_scroll_right')}
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { ContentTypes } from 'librechat-data-provider';
|
||||
import type { TAttachment, TMessageContentParts } from 'librechat-data-provider';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import type { TAttachment, TMessageContentParts } from 'librechat-data-provider';
|
||||
import ContentParts from '../ContentParts';
|
||||
|
||||
jest.mock('~/hooks', () => ({
|
||||
|
|
@ -53,7 +53,14 @@ jest.mock('lucide-react', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('@librechat/client', () => ({
|
||||
Button: ({ children }: { children?: React.ReactNode }) => <button>{children}</button>,
|
||||
Button: ({
|
||||
children,
|
||||
variant: _variant,
|
||||
size: _size,
|
||||
...props
|
||||
}: React.ComponentProps<'button'> & { variant?: string; size?: string }) => (
|
||||
<button {...props}>{children}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../Parts', () => ({
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ jest.mock('~/Providers', () => ({
|
|||
// Mock handleUIAction utility
|
||||
jest.mock('~/utils', () => ({
|
||||
handleUIAction: jest.fn(),
|
||||
cn: (...args: unknown[]) => args.filter(Boolean).join(' '),
|
||||
}));
|
||||
|
||||
// Mock scrollTo
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ import {
|
|||
import {
|
||||
Button,
|
||||
OGDialog,
|
||||
Textarea,
|
||||
OGDialogContent,
|
||||
OGDialogTitle,
|
||||
TooltipAnchor,
|
||||
ThumbUpIcon,
|
||||
ThumbDownIcon,
|
||||
} from '@librechat/client';
|
||||
|
|
@ -55,19 +57,19 @@ function FeedbackOptionButton({
|
|||
const label = localize(tag.label as Parameters<typeof localize>[0]);
|
||||
|
||||
return (
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'flex w-full items-center gap-3 rounded-xl p-2 text-text-secondary transition-colors duration-200 hover:bg-surface-hover hover:text-text-primary',
|
||||
'h-auto w-full justify-start gap-3 rounded-xl p-2 text-text-secondary transition-colors duration-200 hover:bg-surface-hover hover:text-text-primary',
|
||||
active && 'bg-surface-hover font-semibold text-text-primary',
|
||||
)}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
aria-label={label}
|
||||
aria-pressed={active}
|
||||
>
|
||||
<Icon size="19" bold={active} aria-hidden="true" />
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -146,16 +148,17 @@ function FeedbackButtons({
|
|||
<Ariakit.PopoverAnchor
|
||||
store={upStore}
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={buttonClasses(feedback?.rating === 'thumbsUp', isLast)}
|
||||
onClick={handleThumbsUpClick}
|
||||
type="button"
|
||||
title={localize('com_ui_feedback_positive')}
|
||||
aria-pressed={feedback?.rating === 'thumbsUp'}
|
||||
aria-haspopup="menu"
|
||||
>
|
||||
<ThumbUpIcon size="19" bold={feedback?.rating === 'thumbsUp'} />
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Ariakit.Popover
|
||||
|
|
@ -180,16 +183,17 @@ function FeedbackButtons({
|
|||
<Ariakit.PopoverAnchor
|
||||
store={downStore}
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={buttonClasses(feedback?.rating === 'thumbsDown', isLast)}
|
||||
onClick={handleThumbsDownClick}
|
||||
type="button"
|
||||
title={localize('com_ui_feedback_negative')}
|
||||
aria-pressed={feedback?.rating === 'thumbsDown'}
|
||||
aria-haspopup="menu"
|
||||
>
|
||||
<ThumbDownIcon size="19" bold={feedback?.rating === 'thumbsDown'} />
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Ariakit.Popover
|
||||
|
|
@ -216,7 +220,7 @@ function FeedbackButtons({
|
|||
|
||||
function buttonClasses(isActive: boolean, isLast: boolean) {
|
||||
return cn(
|
||||
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
|
||||
'hover-button size-auto rounded-lg p-1.5 text-text-secondary-alt',
|
||||
'hover:text-text-primary hover:bg-surface-hover',
|
||||
'group-hover:visible group-focus-within:visible group-[.final-completion]:visible',
|
||||
!isLast &&
|
||||
|
|
@ -284,21 +288,27 @@ export default function Feedback({
|
|||
? localize('com_ui_feedback_positive')
|
||||
: localize('com_ui_feedback_negative');
|
||||
return (
|
||||
<button
|
||||
className={buttonClasses(true, isLast)}
|
||||
onClick={() => {
|
||||
if (isThumbsUp) {
|
||||
handleButtonFeedback(undefined);
|
||||
} else {
|
||||
setOpenDialog(true);
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
title={label}
|
||||
aria-pressed="true"
|
||||
>
|
||||
<Icon size="19" bold />
|
||||
</button>
|
||||
<TooltipAnchor
|
||||
description={label}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={buttonClasses(true, isLast)}
|
||||
onClick={() => {
|
||||
if (isThumbsUp) {
|
||||
handleButtonFeedback(undefined);
|
||||
} else {
|
||||
setOpenDialog(true);
|
||||
}
|
||||
}}
|
||||
aria-label={label}
|
||||
aria-pressed="true"
|
||||
>
|
||||
<Icon size="19" bold />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -319,8 +329,8 @@ export default function Feedback({
|
|||
<OGDialogTitle className="text-token-text-primary text-lg font-semibold leading-6">
|
||||
{localize('com_ui_feedback_more_information')}
|
||||
</OGDialogTitle>
|
||||
<textarea
|
||||
className="w-full rounded-xl border border-border-light bg-transparent p-2 text-text-primary"
|
||||
<Textarea
|
||||
className="h-auto w-full rounded-xl border-border-light p-2"
|
||||
value={feedback?.text || ''}
|
||||
onChange={handleTextChange}
|
||||
rows={4}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import { useRecoilState } from 'recoil';
|
|||
import * as Ariakit from '@ariakit/react';
|
||||
import { VisuallyHidden } from '@ariakit/react';
|
||||
import { GitFork, InfoIcon } from 'lucide-react';
|
||||
import { useToastContext } from '@librechat/client';
|
||||
import { ForkOptions } from 'librechat-data-provider';
|
||||
import { GitCommit, GitBranchPlus, ListTree } from 'lucide-react';
|
||||
import { Button, Label, Checkbox, useToastContext } from '@librechat/client';
|
||||
import { TranslationKeys, useLocalize, useNavigateToConvo } from '~/hooks';
|
||||
import { useForkConvoMutation } from '~/data-provider';
|
||||
import { cn } from '~/utils';
|
||||
|
|
@ -153,28 +153,28 @@ const CheckboxOption: React.FC<CheckboxOptionProps> = ({
|
|||
<Ariakit.HovercardAnchor
|
||||
render={
|
||||
<div className="flex items-center">
|
||||
<Ariakit.Checkbox
|
||||
<Checkbox
|
||||
id={id}
|
||||
checked={checked}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
if (value && showToastOnCheck) {
|
||||
onCheckedChange={(value) => {
|
||||
const isChecked = value === true;
|
||||
if (isChecked && showToastOnCheck) {
|
||||
showToast({
|
||||
message: localize('com_ui_fork_remember_checked'),
|
||||
status: 'info',
|
||||
});
|
||||
}
|
||||
onToggle(value);
|
||||
onToggle(isChecked);
|
||||
}}
|
||||
className="h-4 w-4 rounded-sm border border-border-xheavy ring-offset-surface-primary transition duration-300 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-text-primary focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-surface-inverted data-[state=checked]:text-text-inverted"
|
||||
className="transition duration-300 ease-in-out"
|
||||
aria-label={localize(labelKey)}
|
||||
/>
|
||||
<label
|
||||
<Label
|
||||
htmlFor={id}
|
||||
className="ml-2 cursor-pointer select-none text-sm text-text-secondary hover:text-text-primary"
|
||||
className="ml-2 w-auto cursor-pointer select-none break-normal text-sm text-text-secondary hover:text-text-primary"
|
||||
>
|
||||
{localize(labelKey)}
|
||||
</label>
|
||||
</Label>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
@ -227,7 +227,7 @@ export default function Fork({
|
|||
});
|
||||
|
||||
const buttonStyle = cn(
|
||||
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
|
||||
'hover-button size-auto rounded-lg p-1.5 text-text-secondary-alt',
|
||||
'hover:text-text-primary hover:bg-surface-hover',
|
||||
'group-hover:visible group-focus-within:visible group-[.final-completion]:visible',
|
||||
!isLast &&
|
||||
|
|
@ -330,7 +330,9 @@ export default function Fork({
|
|||
<Ariakit.PopoverAnchor
|
||||
store={popoverStore}
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={buttonStyle}
|
||||
onClick={(e) => {
|
||||
if (rememberGlobal) {
|
||||
|
|
@ -351,7 +353,7 @@ export default function Fork({
|
|||
aria-label={localize('com_ui_fork_open_menu')}
|
||||
>
|
||||
<GitFork size="19" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Ariakit.Popover
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ChevronDown } from 'lucide-react';
|
|||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { List, CellMeasurer, CellMeasurerCache } from 'react-virtualized';
|
||||
import { Spinner, TooltipAnchor, NewChatIcon, useMediaQuery } from '@librechat/client';
|
||||
import { Button, Spinner, TooltipAnchor, NewChatIcon, useMediaQuery } from '@librechat/client';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import {
|
||||
useLocalize,
|
||||
|
|
@ -127,14 +127,15 @@ const ChatsHeader: FC<ChatsHeaderProps> = memo(({ isExpanded, onToggle }) => {
|
|||
<TooltipAnchor
|
||||
description={localize('com_ui_new_chat')}
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
className={headerIconButtonClassName}
|
||||
onClick={handleNewChat}
|
||||
>
|
||||
<NewChatIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { Button, Input } from '@librechat/client';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
interface RenameFormProps {
|
||||
titleInput: string;
|
||||
|
|
@ -45,10 +47,12 @@ const RenameForm: React.FC<RenameFormProps> = ({
|
|||
role="form"
|
||||
aria-label={localize('com_ui_rename_conversation')}
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="w-full rounded bg-transparent p-0.5 text-sm leading-tight focus-visible:outline-none"
|
||||
className={cn(
|
||||
'h-auto w-full rounded border-0 bg-transparent p-0.5 text-sm leading-tight focus-visible:outline-none',
|
||||
)}
|
||||
value={titleInput}
|
||||
onChange={(e) => setTitleInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
|
|
@ -56,22 +60,30 @@ const RenameForm: React.FC<RenameFormProps> = ({
|
|||
aria-label={localize('com_ui_new_conversation_title')}
|
||||
/>
|
||||
<div className="flex gap-1" role="toolbar">
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onCancel()}
|
||||
className="rounded-md p-1 hover:opacity-70 focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
className={cn(
|
||||
'size-auto rounded-md p-1 hover:opacity-70 focus:outline-none focus:ring-2 focus:ring-text-primary',
|
||||
)}
|
||||
aria-label={localize('com_ui_cancel')}
|
||||
type="button"
|
||||
>
|
||||
<X className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onSubmit(titleInput)}
|
||||
className="rounded-md p-1 hover:opacity-70 focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
className={cn(
|
||||
'size-auto rounded-md p-1 hover:opacity-70 focus:outline-none focus:ring-2 focus:ring-text-primary',
|
||||
)}
|
||||
aria-label={localize('com_ui_save')}
|
||||
type="button"
|
||||
>
|
||||
<Check className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -86,6 +86,14 @@ jest.mock('@librechat/client', () => ({
|
|||
useMediaQuery: () => false,
|
||||
TooltipAnchor: ({ render }: { render: React.ReactNode }) => render,
|
||||
NewChatIcon: () => <svg data-testid="new-chat-icon" />,
|
||||
Button: ({
|
||||
children,
|
||||
variant: _variant,
|
||||
size: _size,
|
||||
...props
|
||||
}: React.ComponentProps<'button'> & { variant?: string; size?: string }) => (
|
||||
<button {...props}>{children}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('~/data-provider', () => ({
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
BedrockIcon,
|
||||
AssistantIcon,
|
||||
AnthropicIcon,
|
||||
TooltipAnchor,
|
||||
AzureMinimalIcon,
|
||||
CustomMinimalIcon,
|
||||
} from '@librechat/client';
|
||||
|
|
@ -62,8 +63,8 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
|||
const assistantsIcon = {
|
||||
icon: iconURL ? (
|
||||
<div className="relative flex h-6 w-6 items-center justify-center">
|
||||
<div
|
||||
title={assistantName}
|
||||
<TooltipAnchor
|
||||
description={assistantName ?? ''}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
|
|
@ -76,7 +77,7 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
|||
alt={assistantName}
|
||||
style={{ height: '80', width: '80' }}
|
||||
/>
|
||||
</div>
|
||||
</TooltipAnchor>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-6 w-6">
|
||||
|
|
@ -91,8 +92,8 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
|||
const agentsIcon = {
|
||||
icon: iconURL ? (
|
||||
<div className="relative flex h-6 w-6 items-center justify-center">
|
||||
<div
|
||||
title={agentName}
|
||||
<TooltipAnchor
|
||||
description={agentName ?? ''}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
|
|
@ -105,7 +106,7 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
|||
alt={agentName}
|
||||
style={{ height: '80', width: '80' }}
|
||||
/>
|
||||
</div>
|
||||
</TooltipAnchor>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-6 w-6">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Spinner } from '@librechat/client';
|
||||
import { PlugZap, SlidersHorizontal, X } from 'lucide-react';
|
||||
import { Button, Spinner, TooltipAnchor } from '@librechat/client';
|
||||
import type { MCPServerStatus } from 'librechat-data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
|
@ -144,18 +144,23 @@ function CompactStatusDot({ serverStatus, isInitializing }: CompactStatusDotProp
|
|||
function LoadingStatusIcon({ serverName, onCancel, canCancel }: InitializingStatusProps) {
|
||||
if (canCancel) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="group flex size-6 items-center justify-center rounded p-1 hover:bg-status-error-subtle"
|
||||
aria-label={localize('com_ui_cancel')}
|
||||
title={localize('com_ui_cancel')}
|
||||
>
|
||||
<div className="relative size-4">
|
||||
<Spinner className="size-4 text-text-primary group-hover:opacity-0" />
|
||||
<X className="absolute inset-0 size-4 text-text-destructive opacity-0 group-hover:opacity-100" />
|
||||
</div>
|
||||
</button>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_cancel')}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onCancel}
|
||||
className="group size-6 rounded p-1 hover:bg-status-error-subtle"
|
||||
aria-label={localize('com_ui_cancel')}
|
||||
>
|
||||
<div className="relative size-4">
|
||||
<Spinner className="size-4 text-text-primary group-hover:opacity-0" />
|
||||
<X className="absolute inset-0 size-4 text-text-destructive opacity-0 group-hover:opacity-100" />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -183,27 +188,29 @@ function ConnectingSpinner({ serverName }: { serverName: string }) {
|
|||
/** Connect button - shown for disconnected/error states. Uses PlugZap icon. */
|
||||
function ConnectButton({ serverName, onConfigClick }: StatusIconProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onConfigClick}
|
||||
className="flex size-6 items-center justify-center rounded p-1 hover:bg-surface-secondary"
|
||||
className="size-6 rounded p-1 hover:bg-surface-secondary"
|
||||
aria-label={localize('com_nav_mcp_connect_server', { 0: serverName })}
|
||||
>
|
||||
<PlugZap className="size-4 text-text-secondary" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
/** Configure button - shown for connected servers with custom vars. Uses SlidersHorizontal icon. */
|
||||
function ConfigureButton({ serverName, onConfigClick }: StatusIconProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onConfigClick}
|
||||
className="flex size-6 items-center justify-center rounded p-1 hover:bg-surface-secondary"
|
||||
className="size-6 rounded p-1 hover:bg-surface-secondary"
|
||||
aria-label={localize('com_nav_mcp_configure_server', { 0: serverName })}
|
||||
>
|
||||
<SlidersHorizontal className="size-4 text-text-secondary" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,16 +139,17 @@ const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolea
|
|||
)}
|
||||
</InputOTP>
|
||||
</div>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
onClick={() => {
|
||||
setUseBackup(!useBackup);
|
||||
setOtpToken('');
|
||||
}}
|
||||
className="text-sm text-text-primary hover:underline"
|
||||
className="h-auto p-0 text-sm font-normal text-text-primary hover:underline"
|
||||
>
|
||||
{useBackup ? localize('com_ui_use_2fa_code') : localize('com_ui_use_backup_code')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{renderDeleteButton(handleDeleteUser, isDeleting, isLocked || !otpReady, localize)}
|
||||
|
|
@ -166,9 +167,9 @@ const renderInput = (
|
|||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
|
||||
) => (
|
||||
<div className="mb-4">
|
||||
<label className="mb-1 block text-sm font-medium text-text-primary" htmlFor={id}>
|
||||
<Label className="mb-1 text-sm font-medium" htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
</Label>
|
||||
<Input id={id} onChange={onChange} placeholder={value} />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -179,10 +180,13 @@ const renderDeleteButton = (
|
|||
isLocked: boolean,
|
||||
localize: LocalizeFunction,
|
||||
) => (
|
||||
<button
|
||||
<Button
|
||||
variant="destructive"
|
||||
className={cn(
|
||||
'mt-4 flex w-full items-center justify-center rounded-lg bg-surface-tertiary px-4 py-2 transition-all duration-200',
|
||||
isLocked ? 'cursor-not-allowed opacity-30' : 'bg-surface-destructive text-white',
|
||||
'mt-4 w-full gap-0 bg-surface-tertiary text-text-primary transition-all duration-200 hover:bg-surface-tertiary disabled:opacity-100',
|
||||
isLocked
|
||||
? 'cursor-not-allowed opacity-30'
|
||||
: 'bg-surface-destructive text-white hover:bg-surface-destructive-hover',
|
||||
)}
|
||||
onClick={handleDeleteUser}
|
||||
disabled={isDeleting || isLocked}
|
||||
|
|
@ -206,7 +210,7 @@ const renderDeleteButton = (
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
|
||||
export default DeleteAccount;
|
||||
|
|
|
|||
|
|
@ -193,21 +193,23 @@ export default function SharedLinks() {
|
|||
},
|
||||
cell: ({ row }) => {
|
||||
const { title, shareId } = row.original;
|
||||
const link = (
|
||||
<Link
|
||||
to={`/share/${shareId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-1 truncate rounded-sm text-link underline decoration-1 underline-offset-2 hover:decoration-2 focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
>
|
||||
<span className="truncate">{title}</span>
|
||||
<ExternalLink
|
||||
className="size-3 flex-shrink-0 opacity-70 group-hover:opacity-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
to={`/share/${shareId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-1 truncate rounded-sm text-link underline decoration-1 underline-offset-2 hover:decoration-2 focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
title={title}
|
||||
>
|
||||
<span className="truncate">{title}</span>
|
||||
<ExternalLink
|
||||
className="size-3 flex-shrink-0 opacity-70 group-hover:opacity-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Link>
|
||||
{title ? <TooltipAnchor description={title} render={link} /> : link}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
@ -271,17 +273,18 @@ export default function SharedLinks() {
|
|||
<TooltipAnchor
|
||||
description={localize('com_ui_open_source_chat_new_tab')}
|
||||
render={
|
||||
<a
|
||||
href={`/c/${row.original.conversationId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md p-0 transition-colors hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
aria-label={localize('com_ui_open_source_chat_new_tab_title', {
|
||||
title: row.original.title || localize('com_ui_untitled'),
|
||||
})}
|
||||
>
|
||||
<MessageSquare className="size-4" aria-hidden="true" />
|
||||
</a>
|
||||
<Button asChild variant="ghost" className="h-8 w-8 p-0 hover:bg-surface-hover">
|
||||
<a
|
||||
href={`/c/${row.original.conversationId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={localize('com_ui_open_source_chat_new_tab_title', {
|
||||
title: row.original.title || localize('com_ui_untitled'),
|
||||
})}
|
||||
>
|
||||
<MessageSquare className="size-4" aria-hidden="true" />
|
||||
</a>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<TooltipAnchor
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { ShieldEllipsis } from 'lucide-react';
|
|||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Permissions, SystemRoles, PermissionTypes } from 'librechat-data-provider';
|
||||
import {
|
||||
Label,
|
||||
Button,
|
||||
Switch,
|
||||
OGDialog,
|
||||
|
|
@ -13,7 +14,7 @@ import {
|
|||
OGDialogTrigger,
|
||||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import type { Control, UseFormSetValue, UseFormGetValues } from 'react-hook-form';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import { useUpdatePeoplePickerPermissionsMutation } from '~/data-provider';
|
||||
import { useLocalize, useAuthContext, useRoleSelector } from '~/hooks';
|
||||
|
||||
|
|
@ -27,36 +28,20 @@ type LabelControllerProps = {
|
|||
label: string;
|
||||
peoplePickerPerm: Permissions.VIEW_USERS | Permissions.VIEW_GROUPS | Permissions.VIEW_ROLES;
|
||||
control: Control<FormValues, unknown, FormValues>;
|
||||
setValue: UseFormSetValue<FormValues>;
|
||||
getValues: UseFormGetValues<FormValues>;
|
||||
};
|
||||
|
||||
const LabelController: React.FC<LabelControllerProps> = ({
|
||||
control,
|
||||
peoplePickerPerm,
|
||||
label,
|
||||
getValues,
|
||||
setValue,
|
||||
}) => (
|
||||
const LabelController: React.FC<LabelControllerProps> = ({ control, peoplePickerPerm, label }) => (
|
||||
<div className="mb-4 flex items-center justify-between gap-2">
|
||||
<button
|
||||
className="cursor-pointer select-none"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setValue(peoplePickerPerm, !getValues(peoplePickerPerm), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Label htmlFor={peoplePickerPerm} className="w-auto cursor-pointer select-none break-normal">
|
||||
{label}
|
||||
</button>
|
||||
</Label>
|
||||
<Controller
|
||||
name={peoplePickerPerm}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
{...field}
|
||||
id={peoplePickerPerm}
|
||||
checked={field.value ?? false}
|
||||
onCheckedChange={field.onChange}
|
||||
value={(field.value ?? false).toString()}
|
||||
|
|
@ -93,8 +78,6 @@ const PeoplePickerAdminSettings = () => {
|
|||
const {
|
||||
reset,
|
||||
control,
|
||||
setValue,
|
||||
getValues,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<FormValues>({
|
||||
|
|
@ -140,7 +123,7 @@ const PeoplePickerAdminSettings = () => {
|
|||
<OGDialogTrigger asChild>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className="btn btn-neutral border-token-border-light gap-1 rounded-lg font-medium"
|
||||
className="gap-1 rounded-lg font-medium"
|
||||
aria-label={localize('com_ui_admin_settings')}
|
||||
>
|
||||
<ShieldEllipsis className="cursor-pointer" aria-hidden="true" />
|
||||
|
|
@ -179,14 +162,13 @@ const PeoplePickerAdminSettings = () => {
|
|||
control={control}
|
||||
peoplePickerPerm={peoplePickerPerm}
|
||||
label={label}
|
||||
getValues={getValues}
|
||||
setValue={setValue}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
<Button
|
||||
variant="submit"
|
||||
type="button"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
disabled={
|
||||
|
|
@ -194,10 +176,10 @@ const PeoplePickerAdminSettings = () => {
|
|||
isLoading ||
|
||||
(isSelectedCustomRole && (isCustomRoleLoading || isCustomRoleError))
|
||||
}
|
||||
className="btn rounded bg-surface-submit font-bold text-white transition-all hover:bg-surface-submit-hover"
|
||||
className="font-bold"
|
||||
>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Pencil } from 'lucide-react';
|
|||
import { Trans } from 'react-i18next';
|
||||
import {
|
||||
Label,
|
||||
Button,
|
||||
Spinner,
|
||||
OGDialog,
|
||||
TrashIcon,
|
||||
|
|
@ -64,14 +65,16 @@ export default function MemoryCardActions({ memory }: MemoryCardActionsProps) {
|
|||
description={localize('com_ui_edit_memory')}
|
||||
side="top"
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
ref={triggerRef}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={buttonBaseClass}
|
||||
aria-label={localize('com_ui_edit')}
|
||||
onClick={() => setEditOpen(true)}
|
||||
>
|
||||
<Pencil className="size-3.5" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</OGDialogTrigger>
|
||||
|
|
@ -84,7 +87,9 @@ export default function MemoryCardActions({ memory }: MemoryCardActionsProps) {
|
|||
description={localize('com_ui_delete_memory')}
|
||||
side="top"
|
||||
render={
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={buttonBaseClass}
|
||||
aria-label={localize('com_ui_delete')}
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
|
|
@ -94,7 +99,7 @@ export default function MemoryCardActions({ memory }: MemoryCardActionsProps) {
|
|||
) : (
|
||||
<TrashIcon className="size-3.5" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</OGDialogTrigger>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { memo } from 'react';
|
||||
import { cn } from '~/utils';
|
||||
import { Switch } from '@librechat/client';
|
||||
|
||||
interface SkillToggleProps {
|
||||
enabled: boolean;
|
||||
|
|
@ -9,31 +9,12 @@ interface SkillToggleProps {
|
|||
|
||||
function SkillToggle({ enabled, onChange, ariaLabel }: SkillToggleProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={enabled}
|
||||
aria-label={ariaLabel}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onChange();
|
||||
}}
|
||||
<span
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="inline-flex h-9 items-center justify-center rounded-md px-1 transition-colors hover:bg-surface-hover"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'relative inline-flex h-5 w-9 shrink-0 rounded-full transition-colors duration-200',
|
||||
enabled ? 'bg-surface-submit' : 'bg-border-medium',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'pointer-events-none mt-0.5 inline-block size-4 rounded-full bg-surface-fixed shadow-sm transition-transform duration-200',
|
||||
enabled ? 'translate-x-[1.125rem]' : 'translate-x-0.5',
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
<Switch checked={enabled} onCheckedChange={() => onChange()} aria-label={ariaLabel} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState } from 'react';
|
|||
import { AlertTriangle } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
||||
import { Input, Button, TextareaAutosize, useToastContext } from '@librechat/client';
|
||||
import { Input, Label, Button, TextareaAutosize, useToastContext } from '@librechat/client';
|
||||
import {
|
||||
InvocationMode,
|
||||
SKILL_NAME_PATTERN,
|
||||
|
|
@ -11,9 +11,9 @@ import {
|
|||
} from 'librechat-data-provider';
|
||||
import type { TSkill, TCreateSkill, TSkillWarning } from 'librechat-data-provider';
|
||||
import { useCreateSkillMutation } from '~/data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import SkillContentEditor from './SkillContentEditor';
|
||||
import CategorySelector from './CategorySelector';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const DEFAULT_BODY = `# Overview
|
||||
|
|
@ -177,12 +177,12 @@ export default function CreateSkillForm({
|
|||
aria-invalid={errors.name ? 'true' : 'false'}
|
||||
aria-describedby={errors.name ? 'skill-name-error' : undefined}
|
||||
/>
|
||||
<label
|
||||
<Label
|
||||
htmlFor="skill-name"
|
||||
className="pointer-events-none absolute -top-1 left-3 origin-[0] translate-y-3 scale-100 rounded bg-presentation px-1 text-base text-text-secondary transition-transform duration-200 peer-placeholder-shown:translate-y-3 peer-placeholder-shown:scale-100 peer-focus:-translate-y-2 peer-focus:scale-75 peer-focus:text-text-primary peer-[:not(:placeholder-shown)]:-translate-y-2 peer-[:not(:placeholder-shown)]:scale-75"
|
||||
>
|
||||
{localize('com_ui_name')}*
|
||||
</label>
|
||||
</Label>
|
||||
<div
|
||||
id="skill-name-error"
|
||||
className={cn(
|
||||
|
|
@ -217,13 +217,13 @@ export default function CreateSkillForm({
|
|||
}}
|
||||
render={({ field }) => (
|
||||
<div className="flex flex-col">
|
||||
<label
|
||||
<Label
|
||||
htmlFor="skill-description"
|
||||
className="mb-1 text-sm font-medium text-text-secondary"
|
||||
>
|
||||
{localize('com_ui_description')}
|
||||
<span className="ml-0.5 text-text-destructive">*</span>
|
||||
</label>
|
||||
</Label>
|
||||
<TextareaAutosize
|
||||
{...field}
|
||||
id="skill-description"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Info, AlertTriangle } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Info, AlertTriangle } from 'lucide-react';
|
||||
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
||||
import { Input, Button, Skeleton, TextareaAutosize, useToastContext } from '@librechat/client';
|
||||
import {
|
||||
Label,
|
||||
Input,
|
||||
Button,
|
||||
Skeleton,
|
||||
TextareaAutosize,
|
||||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import {
|
||||
InvocationMode,
|
||||
SKILL_NAME_PATTERN,
|
||||
|
|
@ -278,13 +285,13 @@ export default function SkillForm({ skillId }: SkillFormProps) {
|
|||
}}
|
||||
render={({ field }) => (
|
||||
<div className="flex flex-col">
|
||||
<label
|
||||
<Label
|
||||
htmlFor="skill-description"
|
||||
className="mb-1 text-sm font-medium text-text-secondary"
|
||||
>
|
||||
{localize('com_ui_description')}
|
||||
<span className="ml-0.5 text-text-destructive">*</span>
|
||||
</label>
|
||||
</Label>
|
||||
<TextareaAutosize
|
||||
{...field}
|
||||
id="skill-description"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useCallback, useRef } from 'react';
|
||||
import { Button, Spinner } from '@librechat/client';
|
||||
import { Save, FileText, Circle } from 'lucide-react';
|
||||
import { Button, Spinner, Textarea } from '@librechat/client';
|
||||
import { useGetSkillNodeContentQuery, useUpdateSkillNodeContentMutation } from '~/data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
|
@ -101,13 +101,14 @@ export default function SkillFileEditor({ skillId, nodeId, fileName }: SkillFile
|
|||
</Button>
|
||||
</div>
|
||||
<div className="relative flex-1 overflow-hidden">
|
||||
<textarea
|
||||
<Textarea
|
||||
ref={textareaRef}
|
||||
value={displayContent}
|
||||
onChange={handleChange}
|
||||
spellCheck={false}
|
||||
className={cn(
|
||||
'size-full resize-none bg-transparent px-4 py-3 font-mono text-[13px] leading-6 text-text-primary outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring-primary',
|
||||
'size-full resize-none rounded-none border-0 bg-transparent px-4 py-3 font-mono text-[13px] leading-6 text-text-primary outline-none',
|
||||
'focus:ring-0 focus:ring-offset-0 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring-primary',
|
||||
'selection:bg-blue-500/20',
|
||||
)}
|
||||
aria-label={`${localize('com_ui_edit')} ${fileName}`}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
import { useEffect } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { Alert } from '@librechat/client';
|
||||
import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Alert,
|
||||
Input,
|
||||
OGDialog,
|
||||
OGDialogTitle,
|
||||
OGDialogContent,
|
||||
OGDialogDescription,
|
||||
} from '@librechat/client';
|
||||
import type {
|
||||
AssistantsEndpoint,
|
||||
EModelEndpoint,
|
||||
|
|
@ -140,49 +146,32 @@ function AssistantToolsDialog({
|
|||
]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
<OGDialog
|
||||
open={isOpen}
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
setSearchValue('');
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
setSearchValue('');
|
||||
}
|
||||
}}
|
||||
className="relative z-[102]"
|
||||
>
|
||||
{/* The backdrop, rendered as a fixed sibling to the panel container */}
|
||||
<div className="fixed inset-0 bg-surface-primary opacity-60 transition-opacity dark:opacity-80" />
|
||||
{/* Full-screen container to center the panel */}
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<DialogPanel
|
||||
className="relative w-full transform overflow-hidden overflow-y-auto rounded-lg bg-surface-secondary text-left shadow-xl transition-all max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
||||
style={{ minHeight: '610px' }}
|
||||
>
|
||||
<OGDialogContent
|
||||
className="overflow-hidden overflow-y-auto bg-surface-secondary text-left max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
||||
style={{ minHeight: '610px' }}
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-between border-b-[1px] border-border-medium px-4 pb-4 pt-5 sm:p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="text-center sm:text-left">
|
||||
<DialogTitle className="text-lg font-medium leading-6 text-text-primary">
|
||||
<OGDialogTitle className="text-lg font-medium leading-6 text-text-primary">
|
||||
{isAgentTools
|
||||
? localize('com_nav_tool_dialog_agents')
|
||||
: localize('com_nav_tool_dialog')}
|
||||
</DialogTitle>
|
||||
<Description className="text-sm text-text-secondary">
|
||||
</OGDialogTitle>
|
||||
<OGDialogDescription className="text-sm text-text-secondary">
|
||||
{localize('com_nav_tool_dialog_description')}
|
||||
</Description>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="sm:mt-0">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="inline-block rounded-full text-text-secondary transition-colors hover:text-text-primary"
|
||||
aria-label="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<X aria-hidden="true" />
|
||||
</button>
|
||||
</OGDialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -203,12 +192,13 @@ function AssistantToolsDialog({
|
|||
<div className="p-4 sm:p-6 sm:pt-4">
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-center space-x-4">
|
||||
<Search className="h-6 w-6 text-text-tertiary" />
|
||||
<input
|
||||
<Search className="h-6 w-6 text-text-tertiary" aria-hidden="true" />
|
||||
<Input
|
||||
type="text"
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
placeholder={localize('com_nav_tool_search')}
|
||||
aria-label={localize('com_nav_tool_search')}
|
||||
className="w-64 rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -243,9 +233,9 @@ function AssistantToolsDialog({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</OGDialogContent>
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useMemo } from 'react';
|
||||
import { OGDialog, DialogTemplate, useToastContext } from '@librechat/client';
|
||||
import { OGDialog, Button, OGDialogTemplate, useToastContext } from '@librechat/client';
|
||||
import type { TTermsOfService } from 'librechat-data-provider';
|
||||
import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite';
|
||||
import { useAcceptTermsMutation } from '~/data-provider';
|
||||
|
|
@ -63,7 +63,7 @@ const TermsAndConditionsModal = ({
|
|||
|
||||
return (
|
||||
<OGDialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogTemplate
|
||||
<OGDialogTemplate
|
||||
title={title ?? localize('com_ui_terms_and_conditions')}
|
||||
className="w-11/12 max-w-3xl sm:w-3/4 md:w-1/2 lg:w-2/5"
|
||||
showCloseButton={false}
|
||||
|
|
@ -87,18 +87,12 @@ const TermsAndConditionsModal = ({
|
|||
}
|
||||
buttons={
|
||||
<>
|
||||
<button
|
||||
onClick={handleDecline}
|
||||
className="inline-flex h-10 items-center justify-center rounded-lg border border-border-heavy bg-surface-secondary px-4 py-2 text-sm text-text-primary hover:bg-surface-active"
|
||||
>
|
||||
<Button variant="outline" onClick={handleDecline}>
|
||||
{localize('com_ui_decline')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAccept}
|
||||
className="inline-flex h-10 items-center justify-center rounded-lg border border-border-heavy bg-surface-secondary px-4 py-2 text-sm text-text-primary hover:bg-surface-submit hover:text-white focus:bg-surface-submit focus:text-white"
|
||||
>
|
||||
</Button>
|
||||
<Button variant="submit" onClick={handleAccept}>
|
||||
{localize('com_ui_accept')}
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { JSX } from 'react/jsx-runtime';
|
|||
import { Sun, Moon, Monitor } from 'lucide-react';
|
||||
import { ThemeContext, isDark } from '../theme';
|
||||
import { useLocalize } from '../hooks';
|
||||
import { Button } from './Button';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -35,23 +36,19 @@ const Theme = ({ theme, onChange }: { theme: string; onChange: (value: string) =
|
|||
}, [nextTheme, onChange]);
|
||||
|
||||
return (
|
||||
<button
|
||||
className="flex items-center gap-2 rounded-lg p-2 text-text-primary transition-colors hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-text-primary focus-visible:ring-offset-2"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-auto w-auto p-2 text-text-primary"
|
||||
aria-label={localize('com_ui_toggle_theme')}
|
||||
aria-keyshortcuts="Ctrl+Shift+T"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onChange(nextTheme);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onChange(nextTheme);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{themeIcons[theme as ThemeType]}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue