mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
refactor(client): migrate buttons, inputs and labels to @librechat/client
Swap raw <button>, <input> and <label> elements for the @librechat/client Button, Input and Label components across Auth, Chat, Conversations, Endpoints, Nav, Prompts, Skills, Tools and Web. Preserve bespoke geometry and behavior via cn className merging, keep data-testid/aria wiring, and localize previously hardcoded aria-labels. Skip swaps that would break floating-label animations, tiny bespoke controls or inline-text links. Add com_ui_reload_page key.
This commit is contained in:
parent
a1b22b745d
commit
91c40d1907
38 changed files with 324 additions and 230 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { Input } from '@librechat/client';
|
||||
import { Button, Input } from '@librechat/client';
|
||||
import { useDebounce, useLocalize } from '~/hooks';
|
||||
|
||||
/**
|
||||
|
|
@ -89,7 +89,9 @@ const SearchBar: React.FC<SearchBarProps> = ({ value, onSearch, className = '' }
|
|||
</div>
|
||||
{/* Show clear button only when search has value - Google style */}
|
||||
{searchTerm && (
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
className="group absolute right-4 top-1/2 flex size-5 -translate-y-1/2 items-center justify-center rounded-full transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-text-primary focus:ring-offset-2"
|
||||
|
|
@ -101,7 +103,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ value, onSearch, className = '' }
|
|||
strokeWidth={2.5}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Turnstile } from '@marsidev/react-turnstile';
|
||||
import { ThemeContext, SecretInput, Spinner, Button, isDark } from '@librechat/client';
|
||||
import { ThemeContext, SecretInput, Spinner, Button, Input, isDark } from '@librechat/client';
|
||||
import type { TLoginUser, TStartupConfig } from 'librechat-data-provider';
|
||||
import type { TAuthContext } from '~/common';
|
||||
import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider';
|
||||
|
|
@ -96,7 +96,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
|||
>
|
||||
<div className="mb-4">
|
||||
<div className="relative">
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
id="email"
|
||||
autoComplete={useUsernameLogin ? 'username' : 'email'}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useForm } from 'react-hook-form';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { loginPage } from 'librechat-data-provider';
|
||||
import { Turnstile } from '@marsidev/react-turnstile';
|
||||
import { ThemeContext, SecretInput, Spinner, Button, isDark } from '@librechat/client';
|
||||
import { useNavigate, useOutletContext, useLocation } from 'react-router-dom';
|
||||
import { useRegisterUserMutation } from 'librechat-data-provider/react-query';
|
||||
import { loginPage } from 'librechat-data-provider';
|
||||
import { ThemeContext, SecretInput, Spinner, Button, Input, isDark } from '@librechat/client';
|
||||
import type { TRegisterUser, TError } from 'librechat-data-provider';
|
||||
import type { TLoginLayoutContext } from '~/common';
|
||||
import { useLocalize, TranslationKeys } from '~/hooks';
|
||||
|
|
@ -98,7 +98,7 @@ const Registration: React.FC = () => {
|
|||
/>
|
||||
) : (
|
||||
<>
|
||||
<input
|
||||
<Input
|
||||
id={id}
|
||||
type={type}
|
||||
autoComplete={id}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { useForm } from 'react-hook-form';
|
||||
import { useState, ReactNode } from 'react';
|
||||
import { Spinner, Button, Alert } from '@librechat/client';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
import { useRequestPasswordResetMutation } from 'librechat-data-provider/react-query';
|
||||
import { loginPage } from 'librechat-data-provider';
|
||||
import { Spinner, Button, Alert, Input } from '@librechat/client';
|
||||
import { useRequestPasswordResetMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TRequestPasswordReset, TRequestPasswordResetResponse } from 'librechat-data-provider';
|
||||
import type { TLoginLayoutContext } from '~/common';
|
||||
import type { FC } from 'react';
|
||||
import type { TLoginLayoutContext } from '~/common';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const BodyTextWrapper: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
|
|
@ -84,7 +84,7 @@ function RequestPasswordReset() {
|
|||
>
|
||||
<div className="space-y-2">
|
||||
<div className="relative">
|
||||
<input
|
||||
<Input
|
||||
type="email"
|
||||
id="email"
|
||||
autoComplete="off"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,14 @@ import { useSearchParams } from 'react-router-dom';
|
|||
import { useToastContext } from '@librechat/client';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp';
|
||||
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Label } from '@librechat/client';
|
||||
import {
|
||||
Label,
|
||||
Button,
|
||||
InputOTP,
|
||||
InputOTPSlot,
|
||||
InputOTPGroup,
|
||||
InputOTPSeparator,
|
||||
} from '@librechat/client';
|
||||
import { useVerifyTwoFactorTempMutation } from '~/data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
|
|
@ -141,33 +148,36 @@ const TwoFactorScreen: React.FC = React.memo(() => {
|
|||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
variant="submit"
|
||||
aria-label={localize('com_auth_continue')}
|
||||
data-testid="login-button"
|
||||
disabled={isLoading}
|
||||
className="w-full rounded-2xl bg-surface-submit px-4 py-3 text-sm font-medium text-white transition-colors hover:bg-surface-submit-hover disabled:opacity-80"
|
||||
className="w-full rounded-2xl px-4 py-3 text-sm font-medium disabled:opacity-80"
|
||||
>
|
||||
{isLoading ? localize('com_auth_email_verifying_ellipsis') : localize('com_ui_verify')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-center">
|
||||
{!useBackup ? (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
onClick={toggleBackupOn}
|
||||
className="inline-flex p-1 text-sm font-medium text-accent-primary transition-colors hover:text-accent-primary-hover"
|
||||
>
|
||||
{localize('com_ui_use_backup_code')}
|
||||
</button>
|
||||
</Button>
|
||||
) : (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
onClick={toggleBackupOff}
|
||||
className="inline-flex p-1 text-sm font-medium text-accent-primary transition-colors hover:text-accent-primary-hover"
|
||||
>
|
||||
{localize('com_ui_use_2fa_code')}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { Spinner, ThemeSelector } from '@librechat/client';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||
import { Button, Spinner, ThemeSelector } from '@librechat/client';
|
||||
import { useVerifyEmailMutation, useResendVerificationEmail } from '~/data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
|
|
@ -87,13 +87,15 @@ function RequestPasswordReset() {
|
|||
{showResendLink && countdown === 0 && (
|
||||
<p className="text-center text-lg text-text-secondary">
|
||||
{localize('com_auth_email_verification_resend_prompt')}
|
||||
<button
|
||||
className="ml-2 text-link hover:underline"
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
className="ml-2 inline h-auto p-0 text-link"
|
||||
onClick={handleResendEmail}
|
||||
disabled={resendEmailMutation.isLoading}
|
||||
>
|
||||
{localize('com_auth_email_resend_link')}
|
||||
</button>
|
||||
</Button>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FileUp } from 'lucide-react';
|
||||
import { cn } from '~/utils/';
|
||||
import { Button } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
type FileUploadProps = {
|
||||
onFileSelected: (jsonData: Record<string, unknown>) => void;
|
||||
|
|
@ -71,8 +72,10 @@ const FileUpload: React.FC<FileUploadProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleClick}
|
||||
className={cn(
|
||||
'mr-1 flex h-auto cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-normal text-text-secondary transition-colors hover:bg-surface-hover hover:text-status-success focus:ring-text-primary',
|
||||
|
|
@ -83,7 +86,7 @@ const FileUpload: React.FC<FileUploadProps> = ({
|
|||
>
|
||||
<FileUp className="mr-1 flex w-[22px] items-center stroke-1" aria-hidden="true" />
|
||||
<span className="flex text-xs">{statusText}</span>
|
||||
</button>
|
||||
</Button>
|
||||
<input
|
||||
id={`file-upload-${id}`}
|
||||
value=""
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { FileSources } from 'librechat-data-provider';
|
|||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import ProgressCircle from './ProgressCircle';
|
||||
import SourceIcon from './SourceIcon';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
type styleProps = {
|
||||
|
|
@ -29,6 +30,7 @@ const ImagePreview = ({
|
|||
source?: FileSources;
|
||||
alt?: string;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
|
|
@ -179,8 +181,9 @@ const ImagePreview = ({
|
|||
ref={closeButtonRef}
|
||||
onClick={() => handleOpenChange(false)}
|
||||
variant="ghost"
|
||||
className="absolute right-4 top-4 z-20 h-10 w-10 p-0 text-white hover:bg-white/10"
|
||||
aria-label="Close"
|
||||
size="icon"
|
||||
className="absolute right-4 top-4 z-20 text-white hover:bg-white/10"
|
||||
aria-label={localize('com_ui_close')}
|
||||
>
|
||||
<X className="size-5" aria-hidden="true" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -133,11 +133,7 @@ export default function MCPConfigDialog({
|
|||
}}
|
||||
buttons={
|
||||
onRevoke && (
|
||||
<Button
|
||||
onClick={handleRevoke}
|
||||
className="bg-surface-destructive text-white hover:bg-surface-destructive-hover"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Button variant="destructive" onClick={handleRevoke} disabled={isSubmitting}>
|
||||
{localize('com_ui_revoke')}
|
||||
</Button>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useRef, useEffect, useMemo, memo, useCallback } from 'react';
|
||||
import { AutoSizer, List } from 'react-virtualized';
|
||||
import { Spinner, useCombobox } from '@librechat/client';
|
||||
import { useSetRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Input, Spinner, useCombobox } from '@librechat/client';
|
||||
import type { TPromptGroup } from 'librechat-data-provider';
|
||||
import type { PromptOption } from '~/common';
|
||||
import useInitPopoverInput from '~/hooks/Input/useInitPopoverInput';
|
||||
|
|
@ -206,10 +206,10 @@ function PromptsCommand({
|
|||
>
|
||||
<div className="absolute bottom-28 z-10 w-full space-y-2">
|
||||
<div className="popover border-token-border-light rounded-2xl border bg-surface-tertiary-alt p-2 shadow-lg">
|
||||
<input
|
||||
<Input
|
||||
ref={initInputRef}
|
||||
placeholder={localize('com_ui_command_usage_placeholder')}
|
||||
className="mb-1 w-full border-0 bg-surface-tertiary-alt p-2 text-sm text-text-primary focus:outline-none"
|
||||
className="mb-1 h-auto w-full rounded-none border-0 bg-surface-tertiary-alt p-2 text-sm text-text-primary focus:outline-none"
|
||||
autoComplete="off"
|
||||
value={searchValue}
|
||||
onKeyDown={(e) => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { memo, useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
||||
import { ScrollText } from 'lucide-react';
|
||||
import { AutoSizer, List } from 'react-virtualized';
|
||||
import { Spinner, useCombobox } from '@librechat/client';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { Input, Spinner, useCombobox } from '@librechat/client';
|
||||
import type { TSkillSummary } from 'librechat-data-provider';
|
||||
import type { MentionOption } from '~/common';
|
||||
import useInitPopoverInput from '~/hooks/Input/useInitPopoverInput';
|
||||
|
|
@ -281,10 +281,10 @@ function SkillsCommandContent({
|
|||
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-tertiary-alt p-2 shadow-lg">
|
||||
<input
|
||||
<Input
|
||||
ref={initInputRef}
|
||||
placeholder={localize('com_ui_skills_command_placeholder')}
|
||||
className="mb-1 w-full border-0 bg-surface-tertiary-alt p-2 text-sm text-text-primary focus:outline-none"
|
||||
className="mb-1 h-auto w-full rounded-none border-0 bg-surface-tertiary-alt p-2 text-sm text-text-primary focus:outline-none"
|
||||
autoComplete="off"
|
||||
value={searchValue}
|
||||
onKeyDown={(e) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
Input,
|
||||
Label,
|
||||
Button,
|
||||
OGDialog,
|
||||
OGDialogTitle,
|
||||
SelectDropDown,
|
||||
|
|
@ -197,18 +198,12 @@ const EditPresetDialog = ({
|
|||
|
||||
{/* Action buttons */}
|
||||
<div className="flex justify-end gap-2 border-t border-border-medium pt-2 md:pt-4">
|
||||
<button
|
||||
onClick={exportPreset}
|
||||
className="rounded-md border border-border-medium bg-surface-secondary px-3 py-2 text-sm font-medium text-text-primary hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-ring-primary focus:ring-offset-2 md:px-4"
|
||||
>
|
||||
<Button variant="outline" onClick={exportPreset}>
|
||||
{localize('com_endpoint_export')}
|
||||
</button>
|
||||
<button
|
||||
onClick={submitPreset}
|
||||
className="rounded-md bg-surface-submit px-3 py-2 text-sm font-medium text-white hover:bg-surface-submit-hover focus:outline-none focus:ring-2 focus:ring-ring-primary focus:ring-offset-2 md:px-4"
|
||||
>
|
||||
</Button>
|
||||
<Button variant="submit" onClick={submitPreset}>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</OGDialogContent>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useMemo, useCallback } from 'react';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { Button } from '@librechat/client';
|
||||
import CopyButton from '~/components/Messages/Content/CopyButton';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
|
@ -141,22 +142,24 @@ export default function OutputRenderer({ text }: OutputRendererProps) {
|
|||
/>
|
||||
</div>
|
||||
{needsTruncation && (
|
||||
<button
|
||||
type="button"
|
||||
className="mt-1 text-xs text-text-secondary underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy"
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="mt-1 h-auto p-0 text-xs text-text-secondary underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy"
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
>
|
||||
{isExpanded ? localize('com_ui_show_less') : localize('com_ui_show_more')}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
{error && rawError && rawError !== displayText && (
|
||||
<button
|
||||
type="button"
|
||||
className="mt-1 block text-xs text-text-secondary underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy"
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="mt-1 block h-auto p-0 text-xs text-text-secondary underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy"
|
||||
onClick={() => setShowErrorDetails((prev) => !prev)}
|
||||
>
|
||||
{localize('com_ui_details')}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
{showErrorDetails && rawError && (
|
||||
<pre className="mt-2 max-h-[200px] overflow-auto whitespace-pre-wrap break-words font-mono text-xs text-status-error">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import React, { useState, useMemo, memo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { EditIcon, Clipboard, CheckMark, ContinueIcon, RegenerateIcon } from '@librechat/client';
|
||||
import {
|
||||
Button,
|
||||
EditIcon,
|
||||
CheckMark,
|
||||
Clipboard,
|
||||
ContinueIcon,
|
||||
TooltipAnchor,
|
||||
RegenerateIcon,
|
||||
} from '@librechat/client';
|
||||
import type { TConversation, TMessage, TFeedback } from 'librechat-data-provider';
|
||||
import { useGenerationsByLatest, useLocalize } from '~/hooks';
|
||||
import { Fork } from '~/components/Conversations';
|
||||
|
|
@ -84,7 +92,7 @@ const HoverButton = memo(
|
|||
dataTestId,
|
||||
}: HoverButtonProps) => {
|
||||
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 &&
|
||||
|
|
@ -96,17 +104,23 @@ const HoverButton = memo(
|
|||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
id={id}
|
||||
data-testid={dataTestId}
|
||||
className={buttonStyle}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
title={title}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
<TooltipAnchor
|
||||
description={title}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
id={id}
|
||||
data-testid={dataTestId}
|
||||
aria-label={title}
|
||||
className={buttonStyle}
|
||||
onClick={onClick}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{icon}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import { Clipboard, CheckMark } from '@librechat/client';
|
||||
import { Button, Clipboard, CheckMark, TooltipAnchor } from '@librechat/client';
|
||||
import type { TMessage, SearchResultData } from 'librechat-data-provider';
|
||||
import { useLocalize, useCopyToClipboard } from '~/hooks';
|
||||
|
||||
|
|
@ -19,20 +19,30 @@ export default function MinimalHoverButtons({ message, searchResults }: THoverBu
|
|||
|
||||
return (
|
||||
<div className="visible mt-1 flex justify-center gap-1 self-end text-text-tertiary lg:justify-start">
|
||||
<button
|
||||
className="ml-0 flex items-center gap-1.5 rounded-lg p-1.5 text-xs text-text-secondary-alt transition-colors duration-200 hover:bg-surface-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-text-primary group-focus-within:opacity-100 group-hover:opacity-100 [@media(hover:hover)]:opacity-0"
|
||||
onClick={() => copyToClipboard(setIsCopied)}
|
||||
type="button"
|
||||
title={
|
||||
<TooltipAnchor
|
||||
description={
|
||||
isCopied ? localize('com_ui_copied_to_clipboard') : localize('com_ui_copy_to_clipboard')
|
||||
}
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckMark className="h-[19px] w-[19px]" />
|
||||
) : (
|
||||
<Clipboard className="h-[19px] w-[19px]" />
|
||||
)}
|
||||
</button>
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label={
|
||||
isCopied
|
||||
? localize('com_ui_copied_to_clipboard')
|
||||
: localize('com_ui_copy_to_clipboard')
|
||||
}
|
||||
className="ml-0 flex size-auto items-center gap-1.5 rounded-lg p-1.5 text-xs text-text-secondary-alt transition-colors duration-200 hover:bg-surface-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-text-primary group-focus-within:opacity-100 group-hover:opacity-100 [@media(hover:hover)]:opacity-0"
|
||||
onClick={() => copyToClipboard(setIsCopied)}
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckMark className="h-[19px] w-[19px]" />
|
||||
) : (
|
||||
<Clipboard className="h-[19px] w-[19px]" />
|
||||
)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { Button } from '@librechat/client';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
type TSiblingSwitchProps = Pick<TMessageProps, 'siblingIdx' | 'siblingCount' | 'setSiblingIdx'>;
|
||||
|
|
@ -9,6 +11,8 @@ export default function SiblingSwitch({
|
|||
siblingCount,
|
||||
setSiblingIdx,
|
||||
}: TSiblingSwitchProps) {
|
||||
const localize = useLocalize();
|
||||
|
||||
if (siblingIdx === undefined) {
|
||||
return null;
|
||||
} else if (siblingCount === undefined) {
|
||||
|
|
@ -24,7 +28,7 @@ export default function SiblingSwitch({
|
|||
};
|
||||
|
||||
const buttonStyle = cn(
|
||||
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
|
||||
'hover-button h-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',
|
||||
'focus-visible:ring-2 focus-visible:ring-text-primary focus-visible:outline-none',
|
||||
|
|
@ -33,18 +37,18 @@ export default function SiblingSwitch({
|
|||
return siblingCount > 1 ? (
|
||||
<nav
|
||||
className="visible flex items-center justify-center gap-2 self-center pt-0 text-xs"
|
||||
aria-label="Sibling message navigation"
|
||||
aria-label={localize('com_ui_sibling_navigation')}
|
||||
>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={buttonStyle}
|
||||
type="button"
|
||||
onClick={previous}
|
||||
disabled={siblingIdx == 0}
|
||||
aria-label="Previous sibling message"
|
||||
aria-label={localize('com_ui_previous_sibling')}
|
||||
aria-disabled={siblingIdx == 0}
|
||||
>
|
||||
<ChevronLeft size="19" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
<span
|
||||
className="flex-shrink-0 flex-grow tabular-nums"
|
||||
aria-live="polite"
|
||||
|
|
@ -53,16 +57,16 @@ export default function SiblingSwitch({
|
|||
>
|
||||
{siblingIdx + 1} / {siblingCount}
|
||||
</span>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={buttonStyle}
|
||||
type="button"
|
||||
onClick={next}
|
||||
disabled={siblingIdx == siblingCount - 1}
|
||||
aria-label="Next sibling message"
|
||||
aria-label={localize('com_ui_next_sibling')}
|
||||
aria-disabled={siblingIdx == siblingCount - 1}
|
||||
>
|
||||
<ChevronRight size="19" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
</nav>
|
||||
) : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Pin } from 'lucide-react';
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { useToastContext, useMediaQuery } from '@librechat/client';
|
||||
import { Spinner, useToastContext, useMediaQuery } from '@librechat/client';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { useNavigateToConvo, useLocalize, useShiftKey } from '~/hooks';
|
||||
import ConversationEndpointIcon from './ConversationEndpointIcon';
|
||||
|
|
@ -179,19 +179,9 @@ function Conversation({
|
|||
};
|
||||
|
||||
const generatingSpinner = (
|
||||
<svg
|
||||
className="h-5 w-5 flex-shrink-0 animate-spin text-text-primary"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
aria-label={localize('com_ui_generating')}
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
<span role="img" aria-label={localize('com_ui_generating')}>
|
||||
<Spinner className="h-5 w-5 flex-shrink-0 text-text-primary" />
|
||||
</span>
|
||||
);
|
||||
|
||||
let actionVisibilityClassName =
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Button } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type TPagesProps = {
|
||||
pages: number;
|
||||
|
|
@ -15,6 +17,7 @@ export default function Pages({
|
|||
previousPage,
|
||||
setPageNumber,
|
||||
}: TPagesProps) {
|
||||
const localize = useLocalize();
|
||||
const clickHandler =
|
||||
(func: () => Promise<void>) => async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -27,29 +30,35 @@ export default function Pages({
|
|||
|
||||
return pageNumber == 1 && pages == 1 ? null : (
|
||||
<div className="m-auto mb-2 mt-4 flex items-center justify-center gap-2">
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label={localize('com_ui_prev')}
|
||||
onClick={clickHandler(previousPage)}
|
||||
className={
|
||||
'btn btn-small bg-transition m-auto flex gap-2 text-text-primary transition hover:bg-surface-hover disabled:text-text-tertiary' +
|
||||
'm-auto flex gap-2 text-text-primary disabled:text-text-tertiary' +
|
||||
(pageNumber <= 1 ? ' hidden-visibility' : '')
|
||||
}
|
||||
disabled={pageNumber <= 1}
|
||||
>
|
||||
<
|
||||
</button>
|
||||
</Button>
|
||||
<span className="flex-none text-text-tertiary">
|
||||
{pageNumber} / {pages}
|
||||
</span>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label={localize('com_ui_next')}
|
||||
onClick={clickHandler(nextPage)}
|
||||
className={
|
||||
'btn btn-small bg-transition m-auto flex gap-2 text-text-primary transition hover:bg-surface-hover disabled:text-text-tertiary' +
|
||||
'm-auto flex gap-2 text-text-primary disabled:text-text-tertiary' +
|
||||
(pageNumber >= pages ? ' hidden-visibility' : '')
|
||||
}
|
||||
disabled={pageNumber >= pages}
|
||||
>
|
||||
>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,13 +247,15 @@ const ProjectChatsInline = memo(function ProjectChatsInline({
|
|||
/>
|
||||
))}
|
||||
{hasMore && (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onShowAll}
|
||||
className="ml-1 mt-0.5 rounded-md px-2 py-1 text-xs font-medium text-text-secondary outline-none transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-text-primary"
|
||||
className="ml-1 mt-0.5 h-auto rounded-md px-2 py-1 text-xs font-medium text-text-secondary transition-colors hover:bg-transparent hover:text-text-primary"
|
||||
>
|
||||
{localize('com_ui_show_all')}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -453,14 +455,15 @@ const ProjectsSection = ({ toggleNav, isAuthenticated }: ProjectsSectionProps) =
|
|||
|
||||
if (projects.length === 0) {
|
||||
return (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => setIsCreateOpen(true)}
|
||||
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-sm text-text-secondary outline-none transition-colors hover:bg-surface-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-text-primary"
|
||||
className="flex h-auto w-full justify-start gap-2 rounded-lg px-2 py-1.5 text-sm font-normal text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-primary"
|
||||
>
|
||||
<FolderPlus className="h-4 w-4 shrink-0" aria-hidden="true" />
|
||||
<span className="truncate">{localize('com_ui_new_project')}</span>
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -476,13 +479,14 @@ const ProjectsSection = ({ toggleNav, isAuthenticated }: ProjectsSectionProps) =
|
|||
))}
|
||||
{hasMore && (
|
||||
<li className="list-none">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={openProjects}
|
||||
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-xs font-medium text-text-secondary outline-none transition-colors hover:bg-surface-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-text-primary"
|
||||
className="flex h-auto w-full justify-start gap-2 rounded-lg px-2 py-1.5 text-xs font-medium text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-primary"
|
||||
>
|
||||
{localize('com_ui_all_projects')}
|
||||
</button>
|
||||
</Button>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useCreatePresetMutation } from 'librechat-data-provider/react-query';
|
||||
import { OGDialogTemplate, OGDialog, Input, Label, useToastContext } from '@librechat/client';
|
||||
import {
|
||||
OGDialogTemplate,
|
||||
OGDialog,
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import type { TEditPresetProps } from '~/common';
|
||||
import { cn, removeFocusOutlines, cleanupPreset, defaultTextProps } from '~/utils';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
|
|
@ -81,11 +88,11 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) =>
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: submitPreset,
|
||||
selectClasses: 'bg-surface-submit hover:bg-surface-submit-hover text-white',
|
||||
selectText: localize('com_ui_save'),
|
||||
}}
|
||||
buttons={
|
||||
<Button variant="submit" onClick={submitPreset}>
|
||||
{localize('com_ui_save')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</OGDialog>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useMultiSearch } from '@librechat/client';
|
|||
import { Root, Trigger, Content, Portal } from '@radix-ui/react-popover';
|
||||
import type { TPlugin } from 'librechat-data-provider';
|
||||
import MenuItem from '~/components/Chat/Menus/UI/MenuItem';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
type SelectDropDownProps = {
|
||||
|
|
@ -32,6 +33,7 @@ function MultiSelectPop({
|
|||
optionValueKey = 'value',
|
||||
searchPlaceholder,
|
||||
}: SelectDropDownProps) {
|
||||
const localize = useLocalize();
|
||||
const title = _title;
|
||||
const excludeIds = ['select-plugin', 'plugins-label', 'selected-plugins'];
|
||||
|
||||
|
|
@ -71,7 +73,7 @@ function MultiSelectPop({
|
|||
)} */}
|
||||
<span className="flex items-center gap-1">
|
||||
<div className="flex gap-1">
|
||||
{value.length === 0 && 'None selected'}
|
||||
{value.length === 0 && localize('com_ui_none_selected')}
|
||||
{value.map((v, i) => (
|
||||
<div key={i} className="relative">
|
||||
{v.icon ? (
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ function SelectDropDownPop({
|
|||
'hover:bg-surface-hover radix-state-open:bg-surface-hover',
|
||||
'min-w-[200px] max-w-[215px] sm:min-w-full sm:max-w-full',
|
||||
)}
|
||||
aria-label={`Select ${title}`}
|
||||
aria-label={localize('com_ui_select_var', { 0: title })}
|
||||
aria-haspopup="false"
|
||||
>
|
||||
{' '}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState, useRef, useCallback, memo } from 'react';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { Spinner } from '@librechat/client';
|
||||
import { Button, Spinner } from '@librechat/client';
|
||||
import useSvgProcessing from './useSvgProcessing';
|
||||
import useMermaidZoom from './useMermaidZoom';
|
||||
import MermaidDialog from './MermaidDialog';
|
||||
|
|
@ -178,14 +178,15 @@ const Mermaid: React.FC<MermaidProps> = memo(({ children, id, theme }) => {
|
|||
<span className="font-semibold text-text-destructive">
|
||||
{localize('com_ui_mermaid_failed')}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRetry}
|
||||
className="flex items-center gap-1 rounded px-2 py-1 text-xs text-text-secondary hover:bg-surface-hover"
|
||||
className="h-auto gap-1 rounded px-2 py-1 text-xs text-text-secondary hover:bg-surface-hover"
|
||||
>
|
||||
<RefreshCw className="h-3 w-3" />
|
||||
{localize('com_ui_retry')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<pre className="overflow-auto text-xs text-text-destructive">{error.message}</pre>
|
||||
{showCode && (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState } from 'react';
|
||||
import * as Tabs from '@radix-ui/react-tabs';
|
||||
import { X, ChevronLeft } from 'lucide-react';
|
||||
import { useMediaQuery } from '@librechat/client';
|
||||
import { Button, useMediaQuery } from '@librechat/client';
|
||||
import { SettingsTabValues } from 'librechat-data-provider';
|
||||
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
|
||||
import type { TDialogProps } from '~/common';
|
||||
|
|
@ -88,14 +88,15 @@ export default function SettingsDialog({ open, onOpenChange }: TDialogProps) {
|
|||
{localize('com_nav_settings')}
|
||||
</h2>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="rounded-lg p-1 text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-border-xheavy"
|
||||
aria-label={localize('com_ui_close_settings')}
|
||||
className="h-auto w-auto rounded-lg p-1 text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-border-xheavy"
|
||||
>
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
<span className="sr-only">{localize('com_ui_close_settings')}</span>
|
||||
</button>
|
||||
</Button>
|
||||
</DialogTitle>
|
||||
<Tabs.Root
|
||||
value={effectiveTab}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,6 @@ import { RefreshCcw } from 'lucide-react';
|
|||
import { useSetRecoilState } from 'recoil';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp';
|
||||
import type {
|
||||
TRegenerateBackupCodesResponse,
|
||||
TRegenerateBackupCodesRequest,
|
||||
TBackupCode,
|
||||
TUser,
|
||||
} from 'librechat-data-provider';
|
||||
import {
|
||||
InputOTPSeparator,
|
||||
InputOTPGroup,
|
||||
|
|
@ -24,6 +18,12 @@ import {
|
|||
TooltipAnchor,
|
||||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import type {
|
||||
TRegenerateBackupCodesResponse,
|
||||
TRegenerateBackupCodesRequest,
|
||||
TBackupCode,
|
||||
TUser,
|
||||
} from 'librechat-data-provider';
|
||||
import { useRegenerateBackupCodesMutation } from '~/data-provider';
|
||||
import { useAuthContext, useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
|
@ -250,16 +250,17 @@ const BackupCodesItem: React.FC = () => {
|
|||
)}
|
||||
</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 text-text-primary hover:underline"
|
||||
>
|
||||
{useBackup ? localize('com_ui_use_2fa_code') : localize('com_ui_use_backup_code')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -77,12 +77,14 @@ export const DisablePhase: React.FC<DisablePhaseProps> = ({ onDisable, isDisabli
|
|||
{isDisabling && <Spinner className="mr-2" />}
|
||||
{isDisabling ? localize('com_ui_disabling') : localize('com_ui_2fa_disable')}
|
||||
</Button>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
onClick={() => setUseBackup(!useBackup)}
|
||||
className="text-sm text-text-primary hover:underline"
|
||||
className="h-auto p-0 text-sm text-text-primary hover:underline"
|
||||
>
|
||||
{useBackup ? localize('com_ui_use_2fa_code') : localize('com_ui_use_backup_code')}
|
||||
</button>
|
||||
</Button>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -258,7 +258,6 @@ export default function ArchivedChatsTable({
|
|||
isArchived: false,
|
||||
})
|
||||
}
|
||||
title={localize('com_ui_unarchive_conversation')}
|
||||
aria-label={localize('com_ui_unarchive_conversation')}
|
||||
disabled={isUnarchiving}
|
||||
>
|
||||
|
|
@ -276,7 +275,6 @@ export default function ArchivedChatsTable({
|
|||
setDeleteConversation(row.original);
|
||||
setIsDeleteOpen(true);
|
||||
}}
|
||||
title={localize('com_ui_delete_conversation_tooltip')}
|
||||
aria-label={localize('com_ui_delete_conversation_tooltip')}
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
|
|
@ -333,7 +331,11 @@ export default function ArchivedChatsTable({
|
|||
</OGDialogTitle>
|
||||
</OGDialogHeader>
|
||||
<div className="flex justify-end gap-4 pt-4">
|
||||
<Button aria-label="cancel" variant="outline" onClick={() => setIsDeleteOpen(false)}>
|
||||
<Button
|
||||
aria-label={localize('com_ui_cancel')}
|
||||
variant="outline"
|
||||
onClick={() => setIsDeleteOpen(false)}
|
||||
>
|
||||
{localize('com_ui_cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Button } from '@librechat/client';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
|
|
@ -59,13 +60,13 @@ export default function OAuthError() {
|
|||
{localize('com_ui_oauth_error_title') || 'Authentication Failed'}
|
||||
</h1>
|
||||
<p className="mb-6 text-sm text-text-secondary">{getErrorMessage(error)}</p>
|
||||
<button
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => window.close()}
|
||||
className="rounded-md bg-surface-inverted px-4 py-2 text-sm font-medium text-text-inverted hover:bg-surface-inverted-hover focus:outline-none focus:ring-2 focus:ring-ring-primary focus:ring-offset-2"
|
||||
aria-label={localize('com_ui_close_window') || 'Close Window'}
|
||||
aria-label={localize('com_ui_close_window')}
|
||||
>
|
||||
{localize('com_ui_close_window') || 'Close Window'}
|
||||
</button>
|
||||
{localize('com_ui_close_window')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { Pencil, Check, Loader2, X } from 'lucide-react';
|
||||
import { Input, TooltipAnchor } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -98,10 +99,31 @@ const PromptName: React.FC<Props> = ({ name, isLoading = false, isError = false,
|
|||
};
|
||||
}, []);
|
||||
|
||||
const displayButton = (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!isLoading && saveStatus !== 'saving') {
|
||||
setIsEditing(true);
|
||||
}
|
||||
}}
|
||||
className="h-8 min-w-0 flex-1 cursor-text truncate pl-2 text-left text-base font-semibold text-text-primary transition-colors hover:text-text-secondary focus:outline-none"
|
||||
aria-label={localize('com_ui_edit') + ': ' + (newName ?? '')}
|
||||
>
|
||||
{newName}
|
||||
</button>
|
||||
);
|
||||
|
||||
const readView = newName ? (
|
||||
<TooltipAnchor description={newName} render={displayButton} />
|
||||
) : (
|
||||
displayButton
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="group/title relative mr-2 flex h-8 min-w-0 flex-1 items-center">
|
||||
{isEditing ? (
|
||||
<input
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={newName ?? ''}
|
||||
|
|
@ -113,19 +135,7 @@ const PromptName: React.FC<Props> = ({ name, isLoading = false, isError = false,
|
|||
aria-label={localize('com_ui_name')}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!isLoading && saveStatus !== 'saving') {
|
||||
setIsEditing(true);
|
||||
}
|
||||
}}
|
||||
className="h-8 min-w-0 flex-1 cursor-text truncate pl-2 text-left text-base font-semibold text-text-primary transition-colors hover:text-text-secondary focus:outline-none"
|
||||
title={newName}
|
||||
aria-label={localize('com_ui_edit') + ': ' + (newName ?? '')}
|
||||
>
|
||||
{newName}
|
||||
</button>
|
||||
readView
|
||||
)}
|
||||
<div className="ml-1.5 flex shrink-0 items-center justify-center">
|
||||
{saveStatus === 'saving' && (
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ const CreatePromptForm = ({
|
|||
<Command onValueChange={(value) => methods.setValue('command', value)} tabIndex={0} />
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button
|
||||
variant="submit"
|
||||
aria-label={localize('com_ui_create_prompt')}
|
||||
className={cn(
|
||||
'w-full sm:w-auto',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
||||
import keyBy from 'lodash/keyBy';
|
||||
import { RotateCcw } from 'lucide-react';
|
||||
import { Button } from '@librechat/client';
|
||||
import {
|
||||
excludedKeys,
|
||||
paramSettings,
|
||||
|
|
@ -181,23 +182,25 @@ export default function Parameters() {
|
|||
})}
|
||||
</div>
|
||||
<div className="mt-4 flex justify-center">
|
||||
<button
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={resetParameters}
|
||||
className="btn btn-neutral flex w-full items-center justify-center gap-2 px-4 py-2 text-sm"
|
||||
className="flex w-full items-center justify-center gap-2 px-4 py-2 text-sm"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" aria-hidden="true" />
|
||||
{localize('com_ui_reset_var', { 0: localize('com_ui_model_parameters') })}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-center">
|
||||
<button
|
||||
<Button
|
||||
variant="submit"
|
||||
onClick={openDialog}
|
||||
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-surface-submit-hover focus:border-surface-submit"
|
||||
className="flex w-full items-center justify-center px-4 py-2 font-semibold"
|
||||
type="button"
|
||||
>
|
||||
{localize('com_endpoint_save_as_preset')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
{preset && (
|
||||
<SaveAsPresetDialog open={isDialogOpen} onOpenChange={setIsDialogOpen} preset={preset} />
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
SKILL_NAME_PATTERN,
|
||||
SKILL_NAME_MAX_LENGTH,
|
||||
SKILL_DESCRIPTION_MAX_LENGTH,
|
||||
} from 'librechat-data-provider';
|
||||
import {
|
||||
Input,
|
||||
Label,
|
||||
Button,
|
||||
OGDialog,
|
||||
OGDialogContent,
|
||||
TextareaAutosize,
|
||||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import {
|
||||
SKILL_NAME_PATTERN,
|
||||
SKILL_NAME_MAX_LENGTH,
|
||||
SKILL_DESCRIPTION_MAX_LENGTH,
|
||||
} from 'librechat-data-provider';
|
||||
import type { TSkill } from 'librechat-data-provider';
|
||||
import type { FormEvent } from 'react';
|
||||
import { useCreateSkillMutation } from '~/data-provider';
|
||||
|
|
@ -124,10 +126,10 @@ export default function CreateSkillDialog({
|
|||
|
||||
{/* Skill name */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="create-skill-name" className="text-sm font-medium text-text-secondary">
|
||||
<Label htmlFor="create-skill-name" className="text-sm font-medium text-text-secondary">
|
||||
{localize('com_ui_name')}
|
||||
</label>
|
||||
<input
|
||||
</Label>
|
||||
<Input
|
||||
id="create-skill-name"
|
||||
placeholder={localize('com_ui_skill_name_placeholder')}
|
||||
aria-invalid={errors.name ? 'true' : 'false'}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useMemo } from 'react';
|
||||
import { format } from 'date-fns';
|
||||
import { TooltipAnchor } from '@librechat/client';
|
||||
import { Button, TooltipAnchor } from '@librechat/client';
|
||||
import { Eye, Code, User, Calendar, EarthIcon, ScrollText } from 'lucide-react';
|
||||
import type { TSkill } from 'librechat-data-provider';
|
||||
import { useLocalize, useAuthContext, useSkillPermissions, useSkillActiveState } from '~/hooks';
|
||||
|
|
@ -139,13 +139,14 @@ export default function SkillDetail({ skill, onEdit, onDelete }: SkillDetailProp
|
|||
/>
|
||||
<ShareSkill skill={skill} />
|
||||
{permissions.canEdit && onEdit && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onEdit}
|
||||
className="inline-flex h-9 items-center justify-center rounded-md border border-border-medium px-3 text-xs font-semibold text-text-primary transition-colors hover:bg-surface-hover"
|
||||
className="h-9 rounded-md border-border-medium px-3 text-xs font-semibold"
|
||||
>
|
||||
{localize('com_ui_edit')}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
{permissions.canDelete && onDelete && (
|
||||
<DeleteSkill skillId={skill._id} skillName={skill.name} onDelete={onDelete} />
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Button } from '@librechat/client';
|
||||
import { XCircle, PlusCircleIcon, Wrench } from 'lucide-react';
|
||||
import type { TPlugin, AgentToolType } from 'librechat-data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
|
@ -51,8 +52,9 @@ function ToolItem({ tool, onAddTool, onRemoveTool, isInstalled = false }: ToolIt
|
|||
{name}
|
||||
</div>
|
||||
{!isInstalled ? (
|
||||
<button
|
||||
className="btn btn-primary relative"
|
||||
<Button
|
||||
variant="submit"
|
||||
className="relative"
|
||||
aria-label={`${localize('com_ui_add')} ${name}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
|
|
@ -60,10 +62,11 @@ function ToolItem({ tool, onAddTool, onRemoveTool, isInstalled = false }: ToolIt
|
|||
{localize('com_ui_add')}
|
||||
<PlusCircleIcon className="flex h-4 w-4 items-center stroke-2" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
) : (
|
||||
<button
|
||||
className="btn relative bg-surface-tertiary hover:bg-surface-hover"
|
||||
<Button
|
||||
variant="outline"
|
||||
className="relative"
|
||||
onClick={handleClick}
|
||||
aria-label={`${localize('com_nav_tool_remove')} ${name}`}
|
||||
>
|
||||
|
|
@ -71,7 +74,7 @@ function ToolItem({ tool, onAddTool, onRemoveTool, isInstalled = false }: ToolIt
|
|||
{localize('com_nav_tool_remove')}
|
||||
<XCircle className="flex h-4 w-4 items-center stroke-2" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import { ChevronDown, FileText } from 'lucide-react';
|
||||
import { Button } from '@librechat/client';
|
||||
import { VisuallyHidden } from '@ariakit/react';
|
||||
import { ChevronDown, FileText } from 'lucide-react';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -70,12 +71,13 @@ function FileHovercardContent({
|
|||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="size-4 shrink-0 text-text-secondary" aria-hidden="true" />
|
||||
<button
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={onClick}
|
||||
className="min-w-0 truncate text-sm font-medium text-text-primary hover:underline"
|
||||
className="h-auto min-w-0 justify-start truncate p-0 text-sm font-medium text-text-primary hover:underline"
|
||||
>
|
||||
{fileName}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
{(fileRelevance != null || (filePages && filePages.length > 0)) && (
|
||||
<div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1">
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { VisuallyHidden } from '@ariakit/react';
|
|||
import { Tools } from 'librechat-data-provider';
|
||||
import { X, Globe, Newspaper, Image, ChevronDown, File, Download } from 'lucide-react';
|
||||
import {
|
||||
Button,
|
||||
OGDialog,
|
||||
AnimatedTabs,
|
||||
OGDialogClose,
|
||||
|
|
@ -752,13 +753,15 @@ export default function Sources(props: SourcesProps) {
|
|||
<div className="mb-2 text-sm text-text-secondary">
|
||||
{localize('com_sources_error_fallback')}
|
||||
</div>
|
||||
<button
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => window.location.reload()}
|
||||
className="hover:bg-surface-primary-hover rounded-md bg-surface-primary px-3 py-1 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
className="hover:bg-surface-primary-hover rounded-md bg-surface-primary px-3 py-1 text-sm text-text-primary"
|
||||
aria-label={localize('com_sources_reload_page')}
|
||||
>
|
||||
{localize('com_ui_refresh')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { Component, ReactNode } from 'react';
|
||||
import { Button } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
|
|
@ -11,6 +13,28 @@ interface State {
|
|||
hasError: boolean;
|
||||
}
|
||||
|
||||
function DefaultFallback() {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col items-center justify-center rounded-lg border border-border-medium bg-surface-secondary p-4 text-center"
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
>
|
||||
{/* eslint-disable-next-line i18next/no-literal-string */}
|
||||
<div className="mb-2 text-sm text-text-secondary">Sources temporarily unavailable</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => window.location.reload()}
|
||||
aria-label={localize('com_ui_reload_page')}
|
||||
>
|
||||
{localize('com_ui_refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
class SourcesErrorBoundary extends Component<Props, State> {
|
||||
state = { hasError: false };
|
||||
|
||||
|
|
@ -30,25 +54,7 @@ class SourcesErrorBoundary extends Component<Props, State> {
|
|||
return this.props.fallback;
|
||||
}
|
||||
|
||||
// Default simple error UI (using localized strings from Sources.tsx fallback)
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col items-center justify-center rounded-lg border border-border-medium bg-surface-secondary p-4 text-center"
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div className="mb-2 text-sm text-text-secondary">Sources temporarily unavailable</div>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="hover:bg-surface-primary-hover rounded-md bg-surface-primary px-3 py-1 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||
aria-label="Reload the page"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
/* eslint-enable i18next/no-literal-string */
|
||||
return <DefaultFallback />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
|
|
|
|||
|
|
@ -1616,6 +1616,7 @@
|
|||
"com_ui_reject": "Reject",
|
||||
"com_ui_reject_reason_placeholder": "Optional: reason for rejecting (shared with the agent)",
|
||||
"com_ui_relevance": "Relevance",
|
||||
"com_ui_reload_page": "Reload the page",
|
||||
"com_ui_remote_access": "Remote Access",
|
||||
"com_ui_remote_agent_role_editor": "Editor",
|
||||
"com_ui_remote_agent_role_editor_desc": "Can view and modify the agent via API",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue