mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-26 21:28:50 +01:00
* better i18n support an internationalization-framework. * removed unused package * auto sort for translation.json * fixed tests with the new locales function * added new CI actions from locize * to use locize a mention in the README.md * to use locize a mention in the README.md * updated README.md and added TRANSLATION.md to the repo * updated TRANSLATION.md badges * updated README.md to go to the TRANSLATION.md when clicking on the Translation Progress badge * updated TRANSLATION.md and added a new issue template. * updated TRANSLATION.md and added a new issue template. * updated issue template to add the iso code link. * updated the new GitHub actions for `locize` * updated label for new issue template --> i18n * fixed type issue * Fix eslint * Fix eslint with key-spacing spacing * fix: error type * fix: handle undefined values in SortFilterHeader component * fix: typing in Image component * fix: handle optional promptGroup in PromptCard component * fix: update localize function to accept string type and remove unnecessary JSX element * fix: update localize function to enforce TranslationKeys type for better type safety * fix: improve type safety and handle null values in Assistants component * fix: enhance null checks for fileId in FilesListView component * fix: localize 'Go back' button text in FilesListView component * fix: update aria-label for menu buttons and add translation for 'Close Menu' * docs: add Reasoning UI section for Chain-of-Thought AI models in README * fix: enhance type safety by adding type for message in MultiMessage component * fix: improve null checks and optional chaining in useAutoSave hook * fix: improve handling of optional properties in cleanupPreset function * fix: ensure isFetchingNextPage defaults to false and improve null checks for messages in Search component * fix: enhance type safety and null checks in useBuildMessageTree hook --------- Co-authored-by: Danny Avila <danny@librechat.ai>
190 lines
5.5 KiB
TypeScript
190 lines
5.5 KiB
TypeScript
import React from 'react';
|
|
import { format } from 'date-fns';
|
|
import { Layers3, Crown, Zap } from 'lucide-react';
|
|
import type { TPrompt, TPromptGroup } from 'librechat-data-provider';
|
|
import { Tag, TooltipAnchor, Label } from '~/components/ui';
|
|
import { useLocalize } from '~/hooks';
|
|
import { cn } from '~/utils';
|
|
|
|
const CombinedStatusIcon = ({ description }: { description: string }) => (
|
|
<TooltipAnchor
|
|
description={description}
|
|
aria-label={description}
|
|
render={
|
|
<div className="flex items-center justify-center">
|
|
<Crown className="h-4 w-4 text-amber-500" />
|
|
</div>
|
|
}
|
|
></TooltipAnchor>
|
|
);
|
|
|
|
const VersionTags = ({ tags }: { tags: string[] }) => {
|
|
const localize = useLocalize();
|
|
const isLatestAndProduction = tags.includes('latest') && tags.includes('production');
|
|
|
|
if (isLatestAndProduction) {
|
|
return (
|
|
<span className="absolute bottom-3 right-3">
|
|
<CombinedStatusIcon description={localize('com_ui_latest_production_version')} />
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<span className="flex gap-1 text-sm">
|
|
{tags.map((tag, i) => (
|
|
<TooltipAnchor
|
|
description={
|
|
tag === 'production'
|
|
? localize('com_ui_currently_production')
|
|
: localize('com_ui_latest_version')
|
|
}
|
|
key={`${tag}-${i}`}
|
|
aria-label={
|
|
tag === 'production'
|
|
? localize('com_ui_currently_production')
|
|
: localize('com_ui_latest_version')
|
|
}
|
|
render={
|
|
<Tag
|
|
label={tag}
|
|
className={cn(
|
|
'w-24 justify-center border border-transparent',
|
|
tag === 'production'
|
|
? 'bg-green-100 text-green-500 dark:border-green-500 dark:bg-transparent dark:text-green-500'
|
|
: 'bg-blue-100 text-blue-500 dark:border-blue-500 dark:bg-transparent dark:text-blue-500',
|
|
)}
|
|
labelClassName="flex items-center m-0 justify-center gap-1"
|
|
LabelNode={(() => {
|
|
if (tag === 'production') {
|
|
return (
|
|
<div className="flex items-center">
|
|
<span className="slow-pulse size-2 rounded-full bg-green-400" />
|
|
</div>
|
|
);
|
|
}
|
|
if (tag === 'latest') {
|
|
return (
|
|
<div className="flex items-center">
|
|
<Zap className="size-4" />
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
})()}
|
|
/>
|
|
}
|
|
></TooltipAnchor>
|
|
))}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
const VersionCard = ({
|
|
prompt,
|
|
index,
|
|
isSelected,
|
|
totalVersions,
|
|
onClick,
|
|
authorName,
|
|
tags,
|
|
}: {
|
|
prompt: TPrompt;
|
|
index: number;
|
|
isSelected: boolean;
|
|
totalVersions: number;
|
|
onClick: () => void;
|
|
authorName?: string;
|
|
tags: string[];
|
|
}) => {
|
|
const localize = useLocalize();
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
'group relative w-full rounded-lg border border-border-light p-4 transition-all duration-300',
|
|
isSelected
|
|
? 'bg-surface-hover shadow-xl'
|
|
: 'bg-surface-primary shadow-sm hover:bg-surface-secondary',
|
|
)}
|
|
onClick={onClick}
|
|
aria-selected={isSelected}
|
|
role="tab"
|
|
aria-label={localize('com_ui_version_var', { 0: `${totalVersions - index}` })}
|
|
>
|
|
<div className="flex flex-col gap-2">
|
|
<div className="flex items-start justify-between lg:flex-col xl:flex-row">
|
|
<h3 className="font-bold text-text-primary">
|
|
{localize('com_ui_version_var', { 0: `${totalVersions - index}` })}
|
|
</h3>
|
|
<time className="text-xs text-text-secondary" dateTime={prompt.createdAt}>
|
|
{format(new Date(prompt.createdAt), 'yyyy-MM-dd HH:mm')}
|
|
</time>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1 lg:flex-col xl:flex-row">
|
|
{authorName && (
|
|
<Label className="text-left text-xs text-text-secondary">by {authorName}</Label>
|
|
)}
|
|
|
|
{tags.length > 0 && <VersionTags tags={tags} />}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
);
|
|
};
|
|
|
|
const PromptVersions = ({
|
|
prompts,
|
|
group,
|
|
selectionIndex,
|
|
setSelectionIndex,
|
|
}: {
|
|
prompts: TPrompt[];
|
|
group?: TPromptGroup;
|
|
selectionIndex: number;
|
|
setSelectionIndex: React.Dispatch<React.SetStateAction<number>>;
|
|
}) => {
|
|
const localize = useLocalize();
|
|
|
|
return (
|
|
<section className="my-6" aria-label="Prompt Versions">
|
|
<header className="mb-6">
|
|
<h2 className="flex items-center gap-2 text-base font-semibold text-text-primary">
|
|
<Layers3 className="h-5 w-5 text-green-500" />
|
|
{localize('com_ui_versions')}
|
|
</h2>
|
|
</header>
|
|
|
|
<div className="flex flex-col gap-3" role="tablist" aria-label="Version history">
|
|
{prompts.map((prompt: TPrompt, index: number) => {
|
|
const tags: string[] = [];
|
|
|
|
if (index === 0) {
|
|
tags.push('latest');
|
|
}
|
|
|
|
if (prompt._id === group?.productionId) {
|
|
tags.push('production');
|
|
}
|
|
|
|
return (
|
|
<VersionCard
|
|
key={prompt._id}
|
|
prompt={prompt}
|
|
index={index}
|
|
isSelected={index === selectionIndex}
|
|
totalVersions={prompts.length}
|
|
onClick={() => setSelectionIndex(index)}
|
|
authorName={group?.authorName}
|
|
tags={tags}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default PromptVersions;
|