LibreChat/client/src/components/Prompts/PromptVersions.tsx
Ruben Talstra aae413cc71
🌎 i18n: React-i18next & i18next Integration (#5720)
* 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>
2025-02-09 12:05:31 -05:00

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;