mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
* chore: Remove @rollup/plugin-terser from package.json and rollup.config.js * 🔧 chore: Revert Shared Links / Data-Table Changes from #9698 - Added sorting functionality for title and creation date in the Shared Links table. - Implemented debounced search filtering for improved performance. - Updated DataTable integration to support new features and improved user experience. - Refactored delete handling to support bulk deletion of shared links. - Adjusted dialog components for better accessibility and user feedback. * 🗑️ chore: Remove unused translation keys from English locale - Deleted keys related to managing archived chats, deleting archived chats, and opening conversations to streamline the translation file.
This commit is contained in:
parent
a5eff768e3
commit
b8a149e563
8 changed files with 564 additions and 642 deletions
|
|
@ -1,6 +1,17 @@
|
||||||
import { useCallback, useState, useMemo, useEffect } from 'react';
|
import { useCallback, useState, useMemo, useEffect } from 'react';
|
||||||
|
import { Trans } from 'react-i18next';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { TrashIcon, MessageSquare, ExternalLink } from 'lucide-react';
|
import {
|
||||||
|
TrashIcon,
|
||||||
|
MessageSquare,
|
||||||
|
ArrowUpDown,
|
||||||
|
ArrowUp,
|
||||||
|
ArrowDown,
|
||||||
|
ExternalLink,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider';
|
||||||
import {
|
import {
|
||||||
OGDialog,
|
OGDialog,
|
||||||
useToastContext,
|
useToastContext,
|
||||||
|
|
@ -10,162 +21,89 @@ import {
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
OGDialogHeader,
|
OGDialogHeader,
|
||||||
OGDialogTitle,
|
OGDialogTitle,
|
||||||
TooltipAnchor,
|
|
||||||
DataTable,
|
DataTable,
|
||||||
Spinner,
|
Spinner,
|
||||||
Button,
|
Button,
|
||||||
Label,
|
Label,
|
||||||
} from '@librechat/client';
|
} from '@librechat/client';
|
||||||
import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider';
|
|
||||||
import type { ColumnDef, SortingState } from '@tanstack/react-table';
|
|
||||||
import { useDeleteSharedLinkMutation, useSharedLinksQuery } from '~/data-provider';
|
import { useDeleteSharedLinkMutation, useSharedLinksQuery } from '~/data-provider';
|
||||||
import { NotificationSeverity } from '~/common';
|
|
||||||
import { formatDate, cn } from '~/utils';
|
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
import { NotificationSeverity } from '~/common';
|
||||||
|
import { formatDate } from '~/utils';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
const PAGE_SIZE = 25;
|
||||||
|
|
||||||
const DEFAULT_PARAMS: SharedLinksListParams = {
|
const DEFAULT_PARAMS: SharedLinksListParams = {
|
||||||
pageSize: 25,
|
pageSize: PAGE_SIZE,
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
sortBy: 'createdAt',
|
sortBy: 'createdAt',
|
||||||
sortDirection: 'desc',
|
sortDirection: 'desc',
|
||||||
search: '',
|
search: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
type SortKey = 'createdAt' | 'title';
|
|
||||||
const isSortKey = (v: string): v is SortKey => v === 'createdAt' || v === 'title';
|
|
||||||
|
|
||||||
const defaultSort: SortingState = [
|
|
||||||
{
|
|
||||||
id: 'createdAt',
|
|
||||||
desc: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type TableColumn<TData, TValue> = ColumnDef<TData, TValue> & {
|
|
||||||
meta?: {
|
|
||||||
className?: string;
|
|
||||||
desktopOnly?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SharedLinks() {
|
export default function SharedLinks() {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
const { showToast } = useToastContext();
|
||||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||||
|
const isSearchEnabled = useRecoilValue(store.search);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
|
||||||
const [deleteRow, setDeleteRow] = useState<SharedLinkItem | null>(null);
|
|
||||||
|
|
||||||
const [queryParams, setQueryParams] = useState<SharedLinksListParams>(DEFAULT_PARAMS);
|
const [queryParams, setQueryParams] = useState<SharedLinksListParams>(DEFAULT_PARAMS);
|
||||||
const [sorting, setSorting] = useState<SortingState>(defaultSort);
|
const [deleteRow, setDeleteRow] = useState<SharedLinkItem | null>(null);
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } =
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } =
|
||||||
useSharedLinksQuery(queryParams, {
|
useSharedLinksQuery(queryParams, {
|
||||||
enabled: isOpen,
|
enabled: isOpen,
|
||||||
keepPreviousData: true,
|
staleTime: 0,
|
||||||
staleTime: 30 * 1000,
|
cacheTime: 5 * 60 * 1000,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [allKnownLinks, setAllKnownLinks] = useState<SharedLinkItem[]>([]);
|
const handleSort = useCallback((sortField: string, sortOrder: 'asc' | 'desc') => {
|
||||||
|
|
||||||
const handleSearchChange = useCallback((value: string) => {
|
|
||||||
setSearchValue(value);
|
|
||||||
setAllKnownLinks([]);
|
|
||||||
setQueryParams((prev) => ({
|
setQueryParams((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
search: value,
|
sortBy: sortField as 'title' | 'createdAt',
|
||||||
|
sortDirection: sortOrder,
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSortingChange = useCallback(
|
const handleFilterChange = useCallback((value: string) => {
|
||||||
(updater: SortingState | ((old: SortingState) => SortingState)) => {
|
const encodedValue = encodeURIComponent(value.trim());
|
||||||
setSorting((prev) => {
|
setQueryParams((prev) => ({
|
||||||
const next = typeof updater === 'function' ? updater(prev) : updater;
|
...prev,
|
||||||
|
search: encodedValue,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const coerced = next;
|
const debouncedFilterChange = useMemo(
|
||||||
const primary = coerced[0];
|
() => debounce(handleFilterChange, 300),
|
||||||
|
[handleFilterChange],
|
||||||
if (data?.pages) {
|
|
||||||
const currentFlattened = data.pages.flatMap((page) => page?.links?.filter(Boolean) ?? []);
|
|
||||||
setAllKnownLinks(currentFlattened);
|
|
||||||
}
|
|
||||||
|
|
||||||
setQueryParams((p) => {
|
|
||||||
let sortBy: SortKey;
|
|
||||||
let sortDirection: 'asc' | 'desc';
|
|
||||||
|
|
||||||
if (primary && isSortKey(primary.id)) {
|
|
||||||
sortBy = primary.id;
|
|
||||||
sortDirection = primary.desc ? 'desc' : 'asc';
|
|
||||||
} else {
|
|
||||||
sortBy = 'createdAt';
|
|
||||||
sortDirection = 'desc';
|
|
||||||
}
|
|
||||||
|
|
||||||
const newParams = {
|
|
||||||
...p,
|
|
||||||
sortBy,
|
|
||||||
sortDirection,
|
|
||||||
};
|
|
||||||
|
|
||||||
return newParams;
|
|
||||||
});
|
|
||||||
|
|
||||||
return coerced;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setQueryParams, data?.pages],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data?.pages) return;
|
return () => {
|
||||||
|
debouncedFilterChange.cancel();
|
||||||
|
};
|
||||||
|
}, [debouncedFilterChange]);
|
||||||
|
|
||||||
const newFlattened = data.pages.flatMap((page) => page?.links?.filter(Boolean) ?? []);
|
const allLinks = useMemo(() => {
|
||||||
|
if (!data?.pages) {
|
||||||
const toAdd = newFlattened.filter(
|
return [];
|
||||||
(link: SharedLinkItem) => !allKnownLinks.some((known) => known.shareId === link.shareId),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (toAdd.length > 0) {
|
|
||||||
setAllKnownLinks((prev) => [...prev, ...toAdd]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return data.pages.flatMap((page) => page.links.filter(Boolean));
|
||||||
}, [data?.pages]);
|
}, [data?.pages]);
|
||||||
|
|
||||||
const displayData = useMemo(() => {
|
|
||||||
const primary = sorting[0];
|
|
||||||
if (!primary || allKnownLinks.length === 0) return allKnownLinks;
|
|
||||||
|
|
||||||
return [...allKnownLinks].sort((a: SharedLinkItem, b: SharedLinkItem) => {
|
|
||||||
let compare: number;
|
|
||||||
if (primary.id === 'createdAt') {
|
|
||||||
const aDate = new Date(a.createdAt || 0);
|
|
||||||
const bDate = new Date(b.createdAt || 0);
|
|
||||||
compare = aDate.getTime() - bDate.getTime();
|
|
||||||
} else if (primary.id === 'title') {
|
|
||||||
compare = (a.title || '').localeCompare(b.title || '');
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return primary.desc ? -compare : compare;
|
|
||||||
});
|
|
||||||
}, [allKnownLinks, sorting]);
|
|
||||||
|
|
||||||
const deleteMutation = useDeleteSharedLinkMutation({
|
const deleteMutation = useDeleteSharedLinkMutation({
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: async () => {
|
||||||
const { shareId } = variables;
|
|
||||||
setAllKnownLinks((prev) => prev.filter((link) => link.shareId !== shareId));
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_shared_link_delete_success'),
|
|
||||||
severity: NotificationSeverity.SUCCESS,
|
|
||||||
});
|
|
||||||
setIsDeleteOpen(false);
|
setIsDeleteOpen(false);
|
||||||
refetch();
|
setDeleteRow(null);
|
||||||
|
await refetch();
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: (error) => {
|
||||||
|
console.error('Delete error:', error);
|
||||||
showToast({
|
showToast({
|
||||||
message: localize('com_ui_share_delete_error'),
|
message: localize('com_ui_share_delete_error'),
|
||||||
severity: NotificationSeverity.ERROR,
|
severity: NotificationSeverity.ERROR,
|
||||||
|
|
@ -173,39 +111,87 @@ export default function SharedLinks() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleFetchNextPage = useCallback(async () => {
|
const handleDelete = useCallback(
|
||||||
if (!hasNextPage || isFetchingNextPage) return;
|
async (selectedRows: SharedLinkItem[]) => {
|
||||||
await fetchNextPage();
|
const validRows = selectedRows.filter(
|
||||||
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
|
(row) => typeof row.shareId === 'string' && row.shareId.length > 0,
|
||||||
|
);
|
||||||
|
|
||||||
const effectiveIsLoading = isLoading && displayData.length === 0;
|
if (validRows.length === 0) {
|
||||||
const effectiveIsFetching = isFetchingNextPage;
|
|
||||||
|
|
||||||
const confirmDelete = useCallback(() => {
|
|
||||||
if (!deleteRow?.shareId) {
|
|
||||||
showToast({
|
showToast({
|
||||||
message: localize('com_ui_share_delete_error'),
|
message: localize('com_ui_no_valid_items'),
|
||||||
severity: NotificationSeverity.WARNING,
|
severity: NotificationSeverity.WARNING,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
deleteMutation.mutate({ shareId: deleteRow.shareId });
|
|
||||||
}, [deleteMutation, deleteRow, localize, showToast]);
|
|
||||||
|
|
||||||
const columns: TableColumn<Record<string, unknown>, unknown>[] = useMemo(
|
try {
|
||||||
|
for (const row of validRows) {
|
||||||
|
await deleteMutation.mutateAsync({ shareId: row.shareId });
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast({
|
||||||
|
message: localize(
|
||||||
|
validRows.length === 1
|
||||||
|
? 'com_ui_shared_link_delete_success'
|
||||||
|
: 'com_ui_shared_link_bulk_delete_success',
|
||||||
|
),
|
||||||
|
severity: NotificationSeverity.SUCCESS,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete shared links:', error);
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_bulk_delete_error'),
|
||||||
|
severity: NotificationSeverity.ERROR,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[deleteMutation, showToast, localize],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFetchNextPage = useCallback(async () => {
|
||||||
|
if (hasNextPage !== true || isFetchingNextPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchNextPage();
|
||||||
|
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
|
||||||
|
|
||||||
|
const confirmDelete = useCallback(() => {
|
||||||
|
if (deleteRow) {
|
||||||
|
handleDelete([deleteRow]);
|
||||||
|
}
|
||||||
|
setIsDeleteOpen(false);
|
||||||
|
}, [deleteRow, handleDelete]);
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
accessorKey: 'title',
|
accessorKey: 'title',
|
||||||
accessorFn: (row: Record<string, unknown>): unknown => {
|
header: () => {
|
||||||
const link = row as SharedLinkItem;
|
const isSorted = queryParams.sortBy === 'title';
|
||||||
return link.title;
|
const sortDirection = queryParams.sortDirection;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||||
|
onClick={() =>
|
||||||
|
handleSort('title', isSorted && sortDirection === 'asc' ? 'desc' : 'asc')
|
||||||
|
}
|
||||||
|
aria-label={localize('com_ui_name_sort')}
|
||||||
|
>
|
||||||
|
{localize('com_ui_name')}
|
||||||
|
{isSorted && sortDirection === 'asc' && (
|
||||||
|
<ArrowUp className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
|
)}
|
||||||
|
{isSorted && sortDirection === 'desc' && (
|
||||||
|
<ArrowDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
|
)}
|
||||||
|
{!isSorted && <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
header: () => (
|
|
||||||
<span className="text-xs text-text-primary sm:text-sm">{localize('com_ui_name')}</span>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const link = row.original as SharedLinkItem;
|
const { title, shareId } = row.original;
|
||||||
const { title, shareId } = link;
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -213,7 +199,7 @@ export default function SharedLinks() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="group flex items-center gap-1 truncate rounded-sm text-blue-600 underline decoration-1 underline-offset-2 hover:decoration-2 focus:outline-none focus:ring-2 focus:ring-ring"
|
className="group flex items-center gap-1 truncate rounded-sm text-blue-600 underline decoration-1 underline-offset-2 hover:decoration-2 focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
aria-label={localize('com_ui_open_link', { 0: title })}
|
title={title}
|
||||||
>
|
>
|
||||||
<span className="truncate">{title}</span>
|
<span className="truncate">{title}</span>
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
|
|
@ -225,139 +211,141 @@ export default function SharedLinks() {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
className: 'min-w-[150px] flex-1',
|
size: '35%',
|
||||||
|
mobileSize: '50%',
|
||||||
},
|
},
|
||||||
enableSorting: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'createdAt',
|
accessorKey: 'createdAt',
|
||||||
accessorFn: (row: Record<string, unknown>): unknown => {
|
header: () => {
|
||||||
const link = row as SharedLinkItem;
|
const isSorted = queryParams.sortBy === 'createdAt';
|
||||||
return link.createdAt;
|
const sortDirection = queryParams.sortDirection;
|
||||||
},
|
return (
|
||||||
header: () => (
|
<Button
|
||||||
<span className="text-xs text-text-primary sm:text-sm">{localize('com_ui_date')}</span>
|
variant="ghost"
|
||||||
),
|
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||||
cell: ({ row }) => {
|
onClick={() =>
|
||||||
const link = row.original as SharedLinkItem;
|
handleSort('createdAt', isSorted && sortDirection === 'asc' ? 'desc' : 'asc')
|
||||||
return formatDate(link.createdAt?.toString() ?? '', isSmallScreen);
|
}
|
||||||
|
aria-label={localize('com_ui_creation_date_sort')}
|
||||||
|
>
|
||||||
|
{localize('com_ui_date')}
|
||||||
|
{isSorted && sortDirection === 'asc' && (
|
||||||
|
<ArrowUp className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
|
)}
|
||||||
|
{isSorted && sortDirection === 'desc' && (
|
||||||
|
<ArrowDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
|
)}
|
||||||
|
{!isSorted && <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen),
|
||||||
meta: {
|
meta: {
|
||||||
className: 'w-32 sm:w-40',
|
size: '10%',
|
||||||
desktopOnly: true,
|
mobileSize: '20%',
|
||||||
},
|
},
|
||||||
enableSorting: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
accessorKey: 'actions',
|
||||||
accessorFn: (row: Record<string, unknown>): unknown => null,
|
|
||||||
header: () => (
|
header: () => (
|
||||||
<span className="text-xs text-text-primary sm:text-sm">
|
<Label className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm">
|
||||||
{localize('com_assistants_actions')}
|
{localize('com_assistants_actions')}
|
||||||
</span>
|
</Label>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
meta: {
|
||||||
const link = row.original as SharedLinkItem;
|
size: '7%',
|
||||||
const { title, conversationId } = link;
|
mobileSize: '25%',
|
||||||
|
},
|
||||||
return (
|
cell: ({ row }) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<TooltipAnchor
|
<a
|
||||||
description={localize('com_ui_view_source')}
|
href={`/c/${row.original.conversationId}`}
|
||||||
render={
|
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-ring"
|
||||||
|
aria-label={`${localize('com_ui_view_source')} - ${row.original.title || localize('com_ui_untitled')}`}
|
||||||
|
>
|
||||||
|
<MessageSquare className="size-4" aria-hidden="true" />
|
||||||
|
</a>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-8 w-8 p-0 hover:bg-surface-hover"
|
className="h-8 w-8 p-0 hover:bg-surface-hover"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(`/c/${conversationId}`, '_blank');
|
setDeleteRow(row.original);
|
||||||
}}
|
|
||||||
aria-label={localize('com_ui_view_source_conversation', { 0: title })}
|
|
||||||
>
|
|
||||||
<MessageSquare className="size-4" />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TooltipAnchor
|
|
||||||
description={localize('com_ui_delete')}
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="h-8 w-8 p-0"
|
|
||||||
onClick={() => {
|
|
||||||
setDeleteRow(link);
|
|
||||||
setIsDeleteOpen(true);
|
setIsDeleteOpen(true);
|
||||||
}}
|
}}
|
||||||
aria-label={localize('com_ui_delete_link_title', { 0: title })}
|
aria-label={localize('com_ui_delete_shared_link', {
|
||||||
|
title: row.original.title || localize('com_ui_untitled'),
|
||||||
|
})}
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
aria-controls="delete-shared-link-dialog"
|
||||||
>
|
>
|
||||||
<TrashIcon className="size-4" />
|
<TrashIcon className="size-4" aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
),
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
className: 'w-24',
|
|
||||||
},
|
|
||||||
enableSorting: false,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[isSmallScreen, localize],
|
[isSmallScreen, localize, queryParams, handleSort],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label id="shared-links-label">{localize('com_nav_shared_links')}</Label>
|
<Label id="shared-links-label">{localize('com_nav_shared_links')}</Label>
|
||||||
|
|
||||||
<OGDialog open={isOpen} onOpenChange={setIsOpen}>
|
<OGDialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<OGDialogTrigger asChild>
|
<OGDialogTrigger asChild onClick={() => setIsOpen(true)}>
|
||||||
<Button aria-labelledby="shared-links-label" variant="outline">
|
<Button aria-labelledby="shared-links-label" variant="outline">
|
||||||
{localize('com_ui_manage')}
|
{localize('com_ui_manage')}
|
||||||
</Button>
|
</Button>
|
||||||
</OGDialogTrigger>
|
</OGDialogTrigger>
|
||||||
<OGDialogContent className={cn('w-11/12 max-w-6xl', isSmallScreen && 'px-1 pb-1')}>
|
|
||||||
|
<OGDialogContent
|
||||||
|
title={localize('com_nav_my_files')}
|
||||||
|
className="w-11/12 max-w-5xl bg-background text-text-primary shadow-2xl"
|
||||||
|
>
|
||||||
<OGDialogHeader>
|
<OGDialogHeader>
|
||||||
<OGDialogTitle>{localize('com_nav_shared_links')}</OGDialogTitle>
|
<OGDialogTitle>{localize('com_nav_shared_links')}</OGDialogTitle>
|
||||||
</OGDialogHeader>
|
</OGDialogHeader>
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={displayData}
|
data={allLinks}
|
||||||
isLoading={effectiveIsLoading}
|
onDelete={handleDelete}
|
||||||
isFetching={effectiveIsFetching}
|
filterColumn="title"
|
||||||
config={{
|
|
||||||
skeleton: { count: 11 },
|
|
||||||
search: {
|
|
||||||
filterColumn: 'title',
|
|
||||||
enableSearch: true,
|
|
||||||
debounce: 300,
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
enableRowSelection: false,
|
|
||||||
showCheckboxes: false,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
filterValue={searchValue}
|
|
||||||
onFilterChange={handleSearchChange}
|
|
||||||
fetchNextPage={handleFetchNextPage}
|
|
||||||
hasNextPage={hasNextPage}
|
hasNextPage={hasNextPage}
|
||||||
isFetchingNextPage={isFetchingNextPage}
|
isFetchingNextPage={isFetchingNextPage}
|
||||||
sorting={sorting}
|
fetchNextPage={handleFetchNextPage}
|
||||||
onSortingChange={handleSortingChange}
|
showCheckboxes={false}
|
||||||
|
onFilterChange={debouncedFilterChange}
|
||||||
|
filterValue={queryParams.search}
|
||||||
|
isLoading={isLoading}
|
||||||
|
enableSearch={isSearchEnabled}
|
||||||
/>
|
/>
|
||||||
</OGDialogContent>
|
</OGDialogContent>
|
||||||
</OGDialog>
|
</OGDialog>
|
||||||
<OGDialog open={isDeleteOpen} onOpenChange={setIsDeleteOpen}>
|
<OGDialog open={isDeleteOpen} onOpenChange={setIsDeleteOpen}>
|
||||||
<OGDialogTemplate
|
<OGDialogTemplate
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
title={localize('com_ui_delete_shared_link')}
|
title={localize('com_ui_delete_shared_link_heading')}
|
||||||
className="w-11/12 max-w-md"
|
className="max-w-[450px]"
|
||||||
main={
|
main={
|
||||||
<div className="flex w-full flex-col items-center gap-2">
|
<>
|
||||||
|
<div
|
||||||
|
id="delete-shared-link-dialog"
|
||||||
|
className="flex w-full flex-col items-center gap-2"
|
||||||
|
>
|
||||||
<div className="grid w-full items-center gap-2">
|
<div className="grid w-full items-center gap-2">
|
||||||
<Label className="text-left text-sm font-medium">
|
<Label htmlFor="dialog-confirm-delete" className="text-left text-sm font-medium">
|
||||||
{localize('com_ui_delete_confirm')} <strong>{deleteRow?.title}</strong>
|
<Trans
|
||||||
|
i18nKey="com_ui_delete_confirm_strong"
|
||||||
|
values={{ title: deleteRow?.title }}
|
||||||
|
components={{ strong: <strong /> }}
|
||||||
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
selection={{
|
selection={{
|
||||||
selectHandler: confirmDelete,
|
selectHandler: confirmDelete,
|
||||||
|
|
|
||||||
|
|
@ -1,406 +1,26 @@
|
||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState } from 'react';
|
||||||
import { QueryKeys } from 'librechat-data-provider';
|
import { OGDialogTemplate, OGDialog, OGDialogTrigger, Button } from '@librechat/client';
|
||||||
import { TrashIcon, ArchiveRestore } from 'lucide-react';
|
import ArchivedChatsTable from './ArchivedChatsTable';
|
||||||
import { useQueryClient, InfiniteData } from '@tanstack/react-query';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
OGDialog,
|
|
||||||
OGDialogTrigger,
|
|
||||||
OGDialogTemplate,
|
|
||||||
OGDialogContent,
|
|
||||||
OGDialogHeader,
|
|
||||||
OGDialogTitle,
|
|
||||||
Label,
|
|
||||||
TooltipAnchor,
|
|
||||||
Spinner,
|
|
||||||
useToastContext,
|
|
||||||
useMediaQuery,
|
|
||||||
DataTable,
|
|
||||||
type TableColumn,
|
|
||||||
} from '@librechat/client';
|
|
||||||
import type { ConversationListParams, TConversation } from 'librechat-data-provider';
|
|
||||||
import type { SortingState } from '@tanstack/react-table';
|
|
||||||
import {
|
|
||||||
useArchiveConvoMutation,
|
|
||||||
useConversationsInfiniteQuery,
|
|
||||||
useDeleteConversationMutation,
|
|
||||||
} from '~/data-provider';
|
|
||||||
import { MinimalIcon } from '~/components/Endpoints';
|
|
||||||
import { NotificationSeverity } from '~/common';
|
|
||||||
import { formatDate, cn } from '~/utils';
|
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
const DEFAULT_PARAMS = {
|
export default function ArchivedChats() {
|
||||||
isArchived: true,
|
|
||||||
sortBy: 'createdAt',
|
|
||||||
sortDirection: 'desc',
|
|
||||||
search: '',
|
|
||||||
} as const satisfies ConversationListParams;
|
|
||||||
|
|
||||||
type SortKey = 'createdAt' | 'title';
|
|
||||||
const isSortKey = (v: string): v is SortKey => v === 'createdAt' || v === 'title';
|
|
||||||
|
|
||||||
const defaultSort: SortingState = [
|
|
||||||
{
|
|
||||||
id: 'createdAt',
|
|
||||||
desc: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper: remove a conversation from all infinite queries whose key starts with the provided root
|
|
||||||
*/
|
|
||||||
function removeConversationFromInfinite(
|
|
||||||
queryClient: ReturnType<typeof useQueryClient>,
|
|
||||||
rootKey: string,
|
|
||||||
conversationId: string,
|
|
||||||
) {
|
|
||||||
const queries = queryClient.getQueryCache().findAll([rootKey], { exact: false });
|
|
||||||
for (const query of queries) {
|
|
||||||
queryClient.setQueryData<
|
|
||||||
InfiniteData<{ conversations: TConversation[]; nextCursor?: string | null }>
|
|
||||||
>(query.queryKey, (old) => {
|
|
||||||
if (!old) return old;
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
pages: old.pages.map((page) => ({
|
|
||||||
...page,
|
|
||||||
conversations: page.conversations.filter((c) => c.conversationId !== conversationId),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ArchivedChatsTable() {
|
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
|
||||||
const { showToast } = useToastContext();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
|
||||||
const [deleteRow, setDeleteRow] = useState<TConversation | null>(null);
|
|
||||||
const [unarchivingId, setUnarchivingId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const [queryParams, setQueryParams] = useState<ConversationListParams>(DEFAULT_PARAMS);
|
|
||||||
const [sorting, setSorting] = useState<SortingState>(defaultSort);
|
|
||||||
const [searchValue, setSearchValue] = useState('');
|
|
||||||
|
|
||||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
|
|
||||||
useConversationsInfiniteQuery(queryParams, {
|
|
||||||
enabled: isOpen,
|
|
||||||
keepPreviousData: false,
|
|
||||||
staleTime: 30 * 1000,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
refetchOnMount: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSearchChange = useCallback((value: string) => {
|
|
||||||
setSearchValue(value);
|
|
||||||
setQueryParams((prev) => ({
|
|
||||||
...prev,
|
|
||||||
search: value,
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSortingChange = useCallback(
|
|
||||||
(updater: SortingState | ((old: SortingState) => SortingState)) => {
|
|
||||||
setSorting((prev) => {
|
|
||||||
const next = typeof updater === 'function' ? updater(prev) : updater;
|
|
||||||
const primary = next[0];
|
|
||||||
setQueryParams((p) => {
|
|
||||||
let sortBy: SortKey = 'createdAt';
|
|
||||||
let sortDirection: 'asc' | 'desc' = 'desc';
|
|
||||||
if (primary && isSortKey(primary.id)) {
|
|
||||||
sortBy = primary.id;
|
|
||||||
sortDirection = primary.desc ? 'desc' : 'asc';
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...p,
|
|
||||||
sortBy,
|
|
||||||
sortDirection,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const flattenedConversations = useMemo(
|
|
||||||
() => data?.pages?.flatMap((page) => page?.conversations?.filter(Boolean) ?? []) ?? [],
|
|
||||||
[data?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const unarchiveMutation = useArchiveConvoMutation({
|
|
||||||
onSuccess: (_res, variables) => {
|
|
||||||
const { conversationId } = variables;
|
|
||||||
if (conversationId) {
|
|
||||||
removeConversationFromInfinite(
|
|
||||||
queryClient,
|
|
||||||
QueryKeys.archivedConversations,
|
|
||||||
conversationId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
queryClient.invalidateQueries([QueryKeys.allConversations]);
|
|
||||||
setUnarchivingId(null);
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_unarchive_error'),
|
|
||||||
severity: NotificationSeverity.ERROR,
|
|
||||||
});
|
|
||||||
setUnarchivingId(null);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const deleteMutation = useDeleteConversationMutation({
|
|
||||||
onSuccess: (_data, variables) => {
|
|
||||||
const { conversationId } = variables;
|
|
||||||
if (conversationId) {
|
|
||||||
removeConversationFromInfinite(
|
|
||||||
queryClient,
|
|
||||||
QueryKeys.archivedConversations,
|
|
||||||
conversationId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_archived_conversation_delete_success'),
|
|
||||||
severity: NotificationSeverity.SUCCESS,
|
|
||||||
});
|
|
||||||
setIsDeleteOpen(false);
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_archive_delete_error'),
|
|
||||||
severity: NotificationSeverity.ERROR,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleFetchNextPage = useCallback(async () => {
|
|
||||||
if (!hasNextPage || isFetchingNextPage) return;
|
|
||||||
await fetchNextPage();
|
|
||||||
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
|
|
||||||
|
|
||||||
const effectiveIsLoading = isLoading;
|
|
||||||
const effectiveIsFetching = isFetchingNextPage;
|
|
||||||
|
|
||||||
const confirmDelete = useCallback(() => {
|
|
||||||
if (!deleteRow?.conversationId) {
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_convo_delete_error'),
|
|
||||||
severity: NotificationSeverity.WARNING,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deleteMutation.mutate({ conversationId: deleteRow.conversationId });
|
|
||||||
}, [deleteMutation, deleteRow, localize, showToast]);
|
|
||||||
|
|
||||||
const handleUnarchive = useCallback(
|
|
||||||
(conversationId: string) => {
|
|
||||||
setUnarchivingId(conversationId);
|
|
||||||
unarchiveMutation.mutate(
|
|
||||||
{ conversationId, isArchived: false },
|
|
||||||
{ onSettled: () => setUnarchivingId(null) },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[unarchiveMutation],
|
|
||||||
);
|
|
||||||
|
|
||||||
const columns: TableColumn<Record<string, unknown>, unknown>[] = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
accessorKey: 'title',
|
|
||||||
accessorFn: (row: Record<string, unknown>): unknown => {
|
|
||||||
const convo = row as TConversation;
|
|
||||||
return convo.title;
|
|
||||||
},
|
|
||||||
header: () => (
|
|
||||||
<span className="text-xs text-text-primary sm:text-sm">
|
|
||||||
{localize('com_nav_archive_name')}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const convo = row.original as TConversation;
|
|
||||||
const { conversationId, title } = convo;
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<MinimalIcon
|
|
||||||
endpoint={convo.endpoint}
|
|
||||||
size={28}
|
|
||||||
isCreatedByUser={false}
|
|
||||||
iconClassName="size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
href={`/c/${conversationId}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex items-center truncate underline"
|
|
||||||
aria-label={localize('com_ui_open_conversation', { 0: title })}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
className: 'min-w-[150px] flex-1',
|
|
||||||
isRowHeader: true,
|
|
||||||
},
|
|
||||||
enableSorting: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'createdAt',
|
|
||||||
accessorFn: (row: Record<string, unknown>): unknown => {
|
|
||||||
const convo = row as TConversation;
|
|
||||||
return convo.createdAt;
|
|
||||||
},
|
|
||||||
header: () => (
|
|
||||||
<span className="text-xs text-text-primary sm:text-sm">
|
|
||||||
{localize('com_nav_archive_created_at')}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const convo = row.original as TConversation;
|
|
||||||
return formatDate(convo.createdAt?.toString() ?? '', isSmallScreen);
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
className: 'w-32 sm:w-40',
|
|
||||||
desktopOnly: true,
|
|
||||||
},
|
|
||||||
enableSorting: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'actions',
|
|
||||||
accessorFn: () => null,
|
|
||||||
header: () => (
|
|
||||||
<span className="text-xs text-text-primary sm:text-sm">
|
|
||||||
{localize('com_assistants_actions')}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const convo = row.original as TConversation;
|
|
||||||
const { title } = convo;
|
|
||||||
const isRowUnarchiving = unarchivingId === convo.conversationId;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-1.5 md:gap-2">
|
|
||||||
<TooltipAnchor
|
|
||||||
description={localize('com_ui_unarchive')}
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="h-9 w-9 p-0 hover:bg-surface-hover md:h-8 md:w-8"
|
|
||||||
onClick={() => {
|
|
||||||
const conversationId = convo.conversationId;
|
|
||||||
if (!conversationId) return;
|
|
||||||
handleUnarchive(conversationId);
|
|
||||||
}}
|
|
||||||
disabled={isRowUnarchiving}
|
|
||||||
aria-label={localize('com_ui_unarchive_conversation_title', { 0: title })}
|
|
||||||
>
|
|
||||||
{isRowUnarchiving ? <Spinner /> : <ArchiveRestore className="size-4" />}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TooltipAnchor
|
|
||||||
description={localize('com_ui_delete')}
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="h-9 w-9 p-0 md:h-8 md:w-8"
|
|
||||||
onClick={() => {
|
|
||||||
setDeleteRow(convo);
|
|
||||||
setIsDeleteOpen(true);
|
|
||||||
}}
|
|
||||||
aria-label={localize('com_ui_delete_conversation_title', { 0: title })}
|
|
||||||
>
|
|
||||||
<TrashIcon className="size-4" />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
className: 'w-24',
|
|
||||||
},
|
|
||||||
enableSorting: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[isSmallScreen, localize, handleUnarchive, unarchivingId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label htmlFor="archived-chats-button" className="text-sm font-medium">
|
<div>{localize('com_nav_archived_chats')}</div>
|
||||||
{localize('com_nav_archived_chats')}
|
|
||||||
</Label>
|
|
||||||
<OGDialog open={isOpen} onOpenChange={setIsOpen}>
|
<OGDialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<OGDialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
<Button
|
<Button variant="outline" aria-label="Archived chats">
|
||||||
id="archived-chats-button"
|
|
||||||
variant="outline"
|
|
||||||
aria-label={localize('com_ui_manage_archived_chats')}
|
|
||||||
>
|
|
||||||
{localize('com_ui_manage')}
|
{localize('com_ui_manage')}
|
||||||
</Button>
|
</Button>
|
||||||
</OGDialogTrigger>
|
</OGDialogTrigger>
|
||||||
<OGDialogContent className={cn('w-11/12 max-w-6xl', isSmallScreen && 'px-1 pb-1')}>
|
|
||||||
<OGDialogHeader>
|
|
||||||
<OGDialogTitle>{localize('com_nav_archived_chats')}</OGDialogTitle>
|
|
||||||
</OGDialogHeader>
|
|
||||||
<DataTable
|
|
||||||
columns={columns}
|
|
||||||
data={flattenedConversations}
|
|
||||||
isLoading={effectiveIsLoading}
|
|
||||||
isFetching={effectiveIsFetching}
|
|
||||||
config={{
|
|
||||||
skeleton: { count: 11 },
|
|
||||||
search: {
|
|
||||||
filterColumn: 'title',
|
|
||||||
enableSearch: true,
|
|
||||||
debounce: 300,
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
enableRowSelection: false,
|
|
||||||
showCheckboxes: false,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
filterValue={searchValue}
|
|
||||||
onFilterChange={handleSearchChange}
|
|
||||||
fetchNextPage={handleFetchNextPage}
|
|
||||||
hasNextPage={hasNextPage}
|
|
||||||
isFetchingNextPage={isFetchingNextPage}
|
|
||||||
sorting={sorting}
|
|
||||||
onSortingChange={handleSortingChange}
|
|
||||||
/>
|
|
||||||
</OGDialogContent>
|
|
||||||
</OGDialog>
|
|
||||||
<OGDialog open={isDeleteOpen} onOpenChange={setIsDeleteOpen}>
|
|
||||||
<OGDialogTemplate
|
<OGDialogTemplate
|
||||||
showCloseButton={false}
|
title={localize('com_nav_archived_chats')}
|
||||||
title={localize('com_ui_delete_archived_chats')}
|
className="max-w-[1000px]"
|
||||||
className="w-11/12 max-w-md"
|
showCancelButton={false}
|
||||||
main={
|
main={<ArchivedChatsTable isOpen={isOpen} onOpenChange={setIsOpen} />}
|
||||||
<div className="flex w-full flex-col items-center gap-2">
|
|
||||||
<div className="grid w-full items-center gap-2">
|
|
||||||
<Label className="text-left text-sm font-medium">
|
|
||||||
{localize('com_ui_delete_confirm')} <strong>{deleteRow?.title}</strong>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
selection={{
|
|
||||||
selectHandler: confirmDelete,
|
|
||||||
selectClasses: `bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 text-white ${
|
|
||||||
deleteMutation.isLoading ? 'cursor-not-allowed opacity-80' : ''
|
|
||||||
}`,
|
|
||||||
selectText: deleteMutation.isLoading ? <Spinner /> : localize('com_ui_delete'),
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</OGDialog>
|
</OGDialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,328 @@
|
||||||
|
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||||
|
import { Trans } from 'react-i18next';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { TrashIcon, ArchiveRestore, ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Label,
|
||||||
|
Spinner,
|
||||||
|
OGDialog,
|
||||||
|
DataTable,
|
||||||
|
TooltipAnchor,
|
||||||
|
useMediaQuery,
|
||||||
|
OGDialogTitle,
|
||||||
|
OGDialogHeader,
|
||||||
|
useToastContext,
|
||||||
|
OGDialogContent,
|
||||||
|
} from '@librechat/client';
|
||||||
|
import type { ConversationListParams, TConversation } from 'librechat-data-provider';
|
||||||
|
import {
|
||||||
|
useConversationsInfiniteQuery,
|
||||||
|
useDeleteConversationMutation,
|
||||||
|
useArchiveConvoMutation,
|
||||||
|
} from '~/data-provider';
|
||||||
|
import { MinimalIcon } from '~/components/Endpoints';
|
||||||
|
import { NotificationSeverity } from '~/common';
|
||||||
|
import { formatDate, logger } from '~/utils';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
const DEFAULT_PARAMS: ConversationListParams = {
|
||||||
|
isArchived: true,
|
||||||
|
sortBy: 'createdAt',
|
||||||
|
sortDirection: 'desc',
|
||||||
|
search: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ArchivedChatsTable({
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||||
|
const { showToast } = useToastContext();
|
||||||
|
const searchState = useRecoilValue(store.search);
|
||||||
|
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||||
|
const [queryParams, setQueryParams] = useState<ConversationListParams>(DEFAULT_PARAMS);
|
||||||
|
const [deleteConversation, setDeleteConversation] = useState<TConversation | null>(null);
|
||||||
|
|
||||||
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } =
|
||||||
|
useConversationsInfiniteQuery(queryParams, {
|
||||||
|
staleTime: 0,
|
||||||
|
cacheTime: 5 * 60 * 1000,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSort = useCallback((sortField: string, sortOrder: 'asc' | 'desc') => {
|
||||||
|
setQueryParams((prev) => ({
|
||||||
|
...prev,
|
||||||
|
sortBy: sortField as 'title' | 'createdAt',
|
||||||
|
sortDirection: sortOrder,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleFilterChange = useCallback((value: string) => {
|
||||||
|
const encodedValue = encodeURIComponent(value.trim());
|
||||||
|
setQueryParams((prev) => ({
|
||||||
|
...prev,
|
||||||
|
search: encodedValue,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const debouncedFilterChange = useMemo(
|
||||||
|
() => debounce(handleFilterChange, 300),
|
||||||
|
[handleFilterChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
debouncedFilterChange.cancel();
|
||||||
|
};
|
||||||
|
}, [debouncedFilterChange]);
|
||||||
|
|
||||||
|
const allConversations = useMemo(() => {
|
||||||
|
if (!data?.pages) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return data.pages.flatMap((page) => page?.conversations?.filter(Boolean) ?? []);
|
||||||
|
}, [data?.pages]);
|
||||||
|
|
||||||
|
const deleteMutation = useDeleteConversationMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
setIsDeleteOpen(false);
|
||||||
|
await refetch();
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_convo_delete_success'),
|
||||||
|
severity: NotificationSeverity.SUCCESS,
|
||||||
|
showIcon: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
logger.error('Error deleting archived conversation:', error);
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_archive_delete_error') as string,
|
||||||
|
severity: NotificationSeverity.ERROR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const unarchiveMutation = useArchiveConvoMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
await refetch();
|
||||||
|
},
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
logger.error('Error unarchiving conversation', error);
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_unarchive_error') as string,
|
||||||
|
severity: NotificationSeverity.ERROR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFetchNextPage = useCallback(async () => {
|
||||||
|
if (!hasNextPage || isFetchingNextPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchNextPage();
|
||||||
|
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'title',
|
||||||
|
header: () => {
|
||||||
|
const isSorted = queryParams.sortBy === 'title';
|
||||||
|
const sortDirection = queryParams.sortDirection;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||||
|
onClick={() =>
|
||||||
|
handleSort('title', isSorted && sortDirection === 'asc' ? 'desc' : 'asc')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{localize('com_nav_archive_name')}
|
||||||
|
{isSorted && sortDirection === 'asc' && (
|
||||||
|
<ArrowUp className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
|
)}
|
||||||
|
{isSorted && sortDirection === 'desc' && (
|
||||||
|
<ArrowDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
|
)}
|
||||||
|
{!isSorted && <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const { conversationId, title } = row.original;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-2 truncate rounded-sm"
|
||||||
|
onClick={() => window.open(`/c/${conversationId}`, '_blank')}
|
||||||
|
>
|
||||||
|
<MinimalIcon
|
||||||
|
endpoint={row.original.endpoint}
|
||||||
|
size={28}
|
||||||
|
isCreatedByUser={false}
|
||||||
|
iconClassName="size-4"
|
||||||
|
/>
|
||||||
|
<span className="underline">{title}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
size: isSmallScreen ? '70%' : '50%',
|
||||||
|
mobileSize: '70%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'createdAt',
|
||||||
|
header: () => {
|
||||||
|
const isSorted = queryParams.sortBy === 'createdAt';
|
||||||
|
const sortDirection = queryParams.sortDirection;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||||
|
onClick={() =>
|
||||||
|
handleSort('createdAt', isSorted && sortDirection === 'asc' ? 'desc' : 'asc')
|
||||||
|
}
|
||||||
|
aria-label={localize('com_nav_archive_created_at_sort')}
|
||||||
|
>
|
||||||
|
{localize('com_nav_archive_created_at')}
|
||||||
|
{isSorted && sortDirection === 'asc' && (
|
||||||
|
<ArrowUp className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
|
)}
|
||||||
|
{isSorted && sortDirection === 'desc' && (
|
||||||
|
<ArrowDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
|
)}
|
||||||
|
{!isSorted && <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen),
|
||||||
|
meta: {
|
||||||
|
size: isSmallScreen ? '30%' : '35%',
|
||||||
|
mobileSize: '30%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'actions',
|
||||||
|
header: () => (
|
||||||
|
<Label className="px-2 py-0 text-xs sm:px-2 sm:py-2 sm:text-sm">
|
||||||
|
{localize('com_assistants_actions')}
|
||||||
|
</Label>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const conversation = row.original;
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<TooltipAnchor
|
||||||
|
description={localize('com_ui_unarchive')}
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="h-8 w-8 p-0 hover:bg-surface-hover"
|
||||||
|
onClick={() =>
|
||||||
|
unarchiveMutation.mutate({
|
||||||
|
conversationId: conversation.conversationId,
|
||||||
|
isArchived: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
title={localize('com_ui_unarchive')}
|
||||||
|
aria-label={localize('com_ui_unarchive')}
|
||||||
|
disabled={unarchiveMutation.isLoading}
|
||||||
|
>
|
||||||
|
{unarchiveMutation.isLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<ArchiveRestore className="size-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipAnchor
|
||||||
|
description={localize('com_ui_delete')}
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="h-8 w-8 p-0 hover:bg-surface-hover"
|
||||||
|
onClick={() => {
|
||||||
|
setDeleteConversation(row.original);
|
||||||
|
setIsDeleteOpen(true);
|
||||||
|
}}
|
||||||
|
title={localize('com_ui_delete')}
|
||||||
|
aria-label={localize('com_ui_delete')}
|
||||||
|
>
|
||||||
|
<TrashIcon className="size-4" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
size: '15%',
|
||||||
|
mobileSize: '25%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[handleSort, isSmallScreen, localize, queryParams, unarchiveMutation],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
data={allConversations}
|
||||||
|
filterColumn="title"
|
||||||
|
onFilterChange={debouncedFilterChange}
|
||||||
|
filterValue={queryParams.search}
|
||||||
|
fetchNextPage={handleFetchNextPage}
|
||||||
|
hasNextPage={hasNextPage}
|
||||||
|
isFetchingNextPage={isFetchingNextPage}
|
||||||
|
isLoading={isLoading}
|
||||||
|
showCheckboxes={false}
|
||||||
|
enableSearch={searchState.enabled === true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<OGDialog open={isDeleteOpen} onOpenChange={onOpenChange}>
|
||||||
|
<OGDialogContent
|
||||||
|
title={localize('com_ui_delete_confirm', {
|
||||||
|
title: deleteConversation?.title ?? localize('com_ui_untitled'),
|
||||||
|
})}
|
||||||
|
className="w-11/12 max-w-md"
|
||||||
|
>
|
||||||
|
<OGDialogHeader>
|
||||||
|
<OGDialogTitle>
|
||||||
|
<Trans
|
||||||
|
i18nKey="com_ui_delete_confirm_strong"
|
||||||
|
values={{ title: deleteConversation?.title }}
|
||||||
|
components={{ strong: <strong /> }}
|
||||||
|
/>
|
||||||
|
</OGDialogTitle>
|
||||||
|
</OGDialogHeader>
|
||||||
|
<div className="flex justify-end gap-4 pt-4">
|
||||||
|
<Button aria-label="cancel" variant="outline" onClick={() => setIsDeleteOpen(false)}>
|
||||||
|
{localize('com_ui_cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() =>
|
||||||
|
deleteMutation.mutate({
|
||||||
|
conversationId: deleteConversation?.conversationId ?? '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={deleteMutation.isLoading}
|
||||||
|
>
|
||||||
|
{deleteMutation.isLoading ? <Spinner /> : localize('com_ui_delete')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</OGDialogContent>
|
||||||
|
</OGDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -396,7 +396,6 @@
|
||||||
"com_nav_archive_created_at": "Date Archived",
|
"com_nav_archive_created_at": "Date Archived",
|
||||||
"com_nav_archive_name": "Name",
|
"com_nav_archive_name": "Name",
|
||||||
"com_nav_archived_chats": "Archived chats",
|
"com_nav_archived_chats": "Archived chats",
|
||||||
"com_ui_manage_archived_chats": "Manage archived chats",
|
|
||||||
"com_nav_at_command": "@-Command",
|
"com_nav_at_command": "@-Command",
|
||||||
"com_nav_at_command_description": "Toggle command \"@\" for switching endpoints, models, presets, etc.",
|
"com_nav_at_command_description": "Toggle command \"@\" for switching endpoints, models, presets, etc.",
|
||||||
"com_nav_audio_play_error": "Error playing audio: {{0}}",
|
"com_nav_audio_play_error": "Error playing audio: {{0}}",
|
||||||
|
|
@ -876,7 +875,6 @@
|
||||||
"com_ui_delete_shared_link": "Delete Shared Link - {{title}}",
|
"com_ui_delete_shared_link": "Delete Shared Link - {{title}}",
|
||||||
"com_ui_delete_shared_link_heading": "Delete Shared Link",
|
"com_ui_delete_shared_link_heading": "Delete Shared Link",
|
||||||
"com_ui_delete_prompt_name": "Delete Prompt - {{name}}",
|
"com_ui_delete_prompt_name": "Delete Prompt - {{name}}",
|
||||||
"com_ui_delete_archived_chats": "Delete archived chat?",
|
|
||||||
"com_ui_delete_success": "Successfully deleted",
|
"com_ui_delete_success": "Successfully deleted",
|
||||||
"com_ui_delete_tool": "Delete Tool",
|
"com_ui_delete_tool": "Delete Tool",
|
||||||
"com_ui_delete_tool_confirm": "Are you sure you want to delete this tool?",
|
"com_ui_delete_tool_confirm": "Are you sure you want to delete this tool?",
|
||||||
|
|
@ -1265,11 +1263,7 @@
|
||||||
"com_ui_share_update_message": "Your name, custom instructions, and any messages you add after sharing stay private.",
|
"com_ui_share_update_message": "Your name, custom instructions, and any messages you add after sharing stay private.",
|
||||||
"com_ui_share_var": "Share {{0}}",
|
"com_ui_share_var": "Share {{0}}",
|
||||||
"com_ui_shared_link_delete_success": "Successfully deleted shared link",
|
"com_ui_shared_link_delete_success": "Successfully deleted shared link",
|
||||||
"com_ui_archived_conversation_delete_success": "Successfully deleted archived conversation",
|
|
||||||
"com_ui_shared_link_not_found": "Shared link not found",
|
"com_ui_shared_link_not_found": "Shared link not found",
|
||||||
"com_ui_open_link": "Open Link {{0}}",
|
|
||||||
"com_ui_view_source_conversation": "View Source Conversation {{0}}",
|
|
||||||
"com_ui_delete_link_title": "Delete Shared Link {{0}}",
|
|
||||||
"com_ui_shared_prompts": "Shared Prompts",
|
"com_ui_shared_prompts": "Shared Prompts",
|
||||||
"com_ui_shop": "Shopping",
|
"com_ui_shop": "Shopping",
|
||||||
"com_ui_show_all": "Show All",
|
"com_ui_show_all": "Show All",
|
||||||
|
|
@ -1400,8 +1394,5 @@
|
||||||
"com_ui_zoom_in": "Zoom in",
|
"com_ui_zoom_in": "Zoom in",
|
||||||
"com_ui_zoom_level": "Zoom level",
|
"com_ui_zoom_level": "Zoom level",
|
||||||
"com_ui_zoom_out": "Zoom out",
|
"com_ui_zoom_out": "Zoom out",
|
||||||
"com_ui_open_conversation": "Open conversation {{0}}",
|
|
||||||
"com_ui_delete_conversation_title": "Delete conversation {{0}}",
|
|
||||||
"com_ui_unarchive_conversation_title": "Unarchive conversation {{0}}",
|
|
||||||
"com_user_message": "You"
|
"com_user_message": "You"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
package-lock.json
generated
1
package-lock.json
generated
|
|
@ -48827,7 +48827,6 @@
|
||||||
"@rollup/plugin-commonjs": "^29.0.0",
|
"@rollup/plugin-commonjs": "^29.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.0",
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
||||||
"@rollup/plugin-replace": "^5.0.5",
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
|
||||||
"@tanstack/react-query": "^4.28.0",
|
"@tanstack/react-query": "^4.28.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@tanstack/react-virtual": "^3.13.13",
|
"@tanstack/react-virtual": "^3.13.13",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf dist",
|
"clean": "rimraf dist",
|
||||||
"build": "npm run clean && rollup -c --bundleConfigAsCjs",
|
"build": "npm run clean && rollup -c --bundleConfigAsCjs",
|
||||||
|
"build:dev": "npm run clean && NODE_ENV=development rollup -c --bundleConfigAsCjs",
|
||||||
"build:watch": "rollup -c -w --bundleConfigAsCjs",
|
"build:watch": "rollup -c -w --bundleConfigAsCjs",
|
||||||
"dev": "rollup -c -w --bundleConfigAsCjs"
|
"dev": "rollup -c -w --bundleConfigAsCjs"
|
||||||
},
|
},
|
||||||
|
|
@ -77,7 +78,6 @@
|
||||||
"@rollup/plugin-commonjs": "^29.0.0",
|
"@rollup/plugin-commonjs": "^29.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.0",
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
||||||
"@rollup/plugin-replace": "^5.0.5",
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
|
||||||
"@tanstack/react-query": "^4.28.0",
|
"@tanstack/react-query": "^4.28.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@tanstack/react-virtual": "^3.13.13",
|
"@tanstack/react-virtual": "^3.13.13",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// ESM bundler config for React components
|
// ESM bundler config for React components
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import alias from '@rollup/plugin-alias';
|
import alias from '@rollup/plugin-alias';
|
||||||
import terser from '@rollup/plugin-terser';
|
|
||||||
import postcss from 'rollup-plugin-postcss';
|
import postcss from 'rollup-plugin-postcss';
|
||||||
import replace from '@rollup/plugin-replace';
|
import replace from '@rollup/plugin-replace';
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
|
|
@ -46,26 +45,23 @@ const plugins = [
|
||||||
clean: true,
|
clean: true,
|
||||||
check: false,
|
check: false,
|
||||||
}),
|
}),
|
||||||
terser({
|
|
||||||
compress: {
|
|
||||||
directives: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
input: 'src/index.ts',
|
input: 'src/index.ts',
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
file: pkg.main,
|
file: pkg.main,
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
sourcemap: true,
|
sourcemap: isDev ? 'inline' : true,
|
||||||
exports: 'named',
|
exports: 'named',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: pkg.module,
|
file: pkg.module,
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
sourcemap: true,
|
sourcemap: isDev ? 'inline' : true,
|
||||||
exports: 'named',
|
exports: 'named',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export * from './InputOTP';
|
||||||
export * from './MultiSearch';
|
export * from './MultiSearch';
|
||||||
export * from './Resizable';
|
export * from './Resizable';
|
||||||
export * from './Select';
|
export * from './Select';
|
||||||
export * from './DataTable/index';
|
export { default as DataTable } from './DataTable';
|
||||||
export { default as Radio } from './Radio';
|
export { default as Radio } from './Radio';
|
||||||
export { default as Badge } from './Badge';
|
export { default as Badge } from './Badge';
|
||||||
export { default as Avatar } from './Avatar';
|
export { default as Avatar } from './Avatar';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue