diff --git a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx index e8f00bcc77..d4020fe715 100644 --- a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx @@ -1,6 +1,17 @@ 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 { 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 { OGDialog, useToastContext, @@ -10,162 +21,89 @@ import { useMediaQuery, OGDialogHeader, OGDialogTitle, - TooltipAnchor, DataTable, Spinner, Button, Label, } 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 { NotificationSeverity } from '~/common'; -import { formatDate, cn } from '~/utils'; import { useLocalize } from '~/hooks'; +import { NotificationSeverity } from '~/common'; +import { formatDate } from '~/utils'; +import store from '~/store'; + +const PAGE_SIZE = 25; const DEFAULT_PARAMS: SharedLinksListParams = { - pageSize: 25, + pageSize: PAGE_SIZE, isPublic: true, sortBy: 'createdAt', sortDirection: 'desc', 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 = ColumnDef & { - meta?: { - className?: string; - desktopOnly?: boolean; - }; -}; - export default function SharedLinks() { const localize = useLocalize(); const { showToast } = useToastContext(); const isSmallScreen = useMediaQuery('(max-width: 768px)'); - - const [isOpen, setIsOpen] = useState(false); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); - const [deleteRow, setDeleteRow] = useState(null); - + const isSearchEnabled = useRecoilValue(store.search); const [queryParams, setQueryParams] = useState(DEFAULT_PARAMS); - const [sorting, setSorting] = useState(defaultSort); - const [searchValue, setSearchValue] = useState(''); + const [deleteRow, setDeleteRow] = useState(null); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const [isOpen, setIsOpen] = useState(false); const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } = useSharedLinksQuery(queryParams, { enabled: isOpen, - keepPreviousData: true, - staleTime: 30 * 1000, + staleTime: 0, + cacheTime: 5 * 60 * 1000, refetchOnWindowFocus: false, refetchOnMount: false, }); - const [allKnownLinks, setAllKnownLinks] = useState([]); - - const handleSearchChange = useCallback((value: string) => { - setSearchValue(value); - setAllKnownLinks([]); + const handleSort = useCallback((sortField: string, sortOrder: 'asc' | 'desc') => { setQueryParams((prev) => ({ ...prev, - search: value, + sortBy: sortField as 'title' | 'createdAt', + sortDirection: sortOrder, })); }, []); - const handleSortingChange = useCallback( - (updater: SortingState | ((old: SortingState) => SortingState)) => { - setSorting((prev) => { - const next = typeof updater === 'function' ? updater(prev) : updater; + const handleFilterChange = useCallback((value: string) => { + const encodedValue = encodeURIComponent(value.trim()); + setQueryParams((prev) => ({ + ...prev, + search: encodedValue, + })); + }, []); - const coerced = next; - const primary = coerced[0]; - - 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], + const debouncedFilterChange = useMemo( + () => debounce(handleFilterChange, 300), + [handleFilterChange], ); useEffect(() => { - if (!data?.pages) return; + return () => { + debouncedFilterChange.cancel(); + }; + }, [debouncedFilterChange]); - const newFlattened = data.pages.flatMap((page) => page?.links?.filter(Boolean) ?? []); - - const toAdd = newFlattened.filter( - (link: SharedLinkItem) => !allKnownLinks.some((known) => known.shareId === link.shareId), - ); - - if (toAdd.length > 0) { - setAllKnownLinks((prev) => [...prev, ...toAdd]); + const allLinks = useMemo(() => { + if (!data?.pages) { + return []; } + + return data.pages.flatMap((page) => page.links.filter(Boolean)); }, [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({ - onSuccess: (data, variables) => { - const { shareId } = variables; - setAllKnownLinks((prev) => prev.filter((link) => link.shareId !== shareId)); - showToast({ - message: localize('com_ui_shared_link_delete_success'), - severity: NotificationSeverity.SUCCESS, - }); + onSuccess: async () => { setIsDeleteOpen(false); - refetch(); + setDeleteRow(null); + await refetch(); }, - onError: () => { + onError: (error) => { + console.error('Delete error:', error); showToast({ message: localize('com_ui_share_delete_error'), severity: NotificationSeverity.ERROR, @@ -173,39 +111,87 @@ export default function SharedLinks() { }, }); + const handleDelete = useCallback( + async (selectedRows: SharedLinkItem[]) => { + const validRows = selectedRows.filter( + (row) => typeof row.shareId === 'string' && row.shareId.length > 0, + ); + + if (validRows.length === 0) { + showToast({ + message: localize('com_ui_no_valid_items'), + severity: NotificationSeverity.WARNING, + }); + return; + } + + 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 || isFetchingNextPage) return; + if (hasNextPage !== true || isFetchingNextPage) { + return; + } await fetchNextPage(); }, [fetchNextPage, hasNextPage, isFetchingNextPage]); - const effectiveIsLoading = isLoading && displayData.length === 0; - const effectiveIsFetching = isFetchingNextPage; - const confirmDelete = useCallback(() => { - if (!deleteRow?.shareId) { - showToast({ - message: localize('com_ui_share_delete_error'), - severity: NotificationSeverity.WARNING, - }); - return; + if (deleteRow) { + handleDelete([deleteRow]); } - deleteMutation.mutate({ shareId: deleteRow.shareId }); - }, [deleteMutation, deleteRow, localize, showToast]); + setIsDeleteOpen(false); + }, [deleteRow, handleDelete]); - const columns: TableColumn, unknown>[] = useMemo( + const columns = useMemo( () => [ { accessorKey: 'title', - accessorFn: (row: Record): unknown => { - const link = row as SharedLinkItem; - return link.title; + header: () => { + const isSorted = queryParams.sortBy === 'title'; + const sortDirection = queryParams.sortDirection; + return ( + + ); }, - header: () => ( - {localize('com_ui_name')} - ), cell: ({ row }) => { - const link = row.original as SharedLinkItem; - const { title, shareId } = link; + const { title, shareId } = row.original; return (
{title} ): unknown => { - const link = row as SharedLinkItem; - return link.createdAt; - }, - header: () => ( - {localize('com_ui_date')} - ), - cell: ({ row }) => { - const link = row.original as SharedLinkItem; - return formatDate(link.createdAt?.toString() ?? '', isSmallScreen); - }, - meta: { - className: 'w-32 sm:w-40', - desktopOnly: true, - }, - enableSorting: true, - }, - { - id: 'actions', - accessorFn: (row: Record): unknown => null, - header: () => ( - - {localize('com_assistants_actions')} - - ), - cell: ({ row }) => { - const link = row.original as SharedLinkItem; - const { title, conversationId } = link; - + header: () => { + const isSorted = queryParams.sortBy === 'createdAt'; + const sortDirection = queryParams.sortDirection; return ( -
- { - window.open(`/c/${conversationId}`, '_blank'); - }} - aria-label={localize('com_ui_view_source_conversation', { 0: title })} - > - - - } - /> - { - setDeleteRow(link); - setIsDeleteOpen(true); - }} - aria-label={localize('com_ui_delete_link_title', { 0: title })} - > - - - } - /> -
+ ); }, + cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen), meta: { - className: 'w-24', + size: '10%', + mobileSize: '20%', }, - enableSorting: false, + }, + { + accessorKey: 'actions', + header: () => ( + + ), + meta: { + size: '7%', + mobileSize: '25%', + }, + cell: ({ row }) => ( +
+ + + +
+ ), }, ], - [isSmallScreen, localize], + [isSmallScreen, localize, queryParams, handleSort], ); return (
+ - + setIsOpen(true)}> - + + {localize('com_nav_shared_links')} -
- + <> + -
+ } selection={{ selectHandler: confirmDelete, diff --git a/client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx b/client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx index e49e9d086c..2e6f6df07d 100644 --- a/client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx +++ b/client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx @@ -1,406 +1,26 @@ -import { useState, useCallback, useMemo } from 'react'; -import { QueryKeys } from 'librechat-data-provider'; -import { TrashIcon, ArchiveRestore } from 'lucide-react'; -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 { useState } from 'react'; +import { OGDialogTemplate, OGDialog, OGDialogTrigger, Button } from '@librechat/client'; +import ArchivedChatsTable from './ArchivedChatsTable'; import { useLocalize } from '~/hooks'; -const DEFAULT_PARAMS = { - 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, - 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() { +export default function ArchivedChats() { const localize = useLocalize(); - const isSmallScreen = useMediaQuery('(max-width: 768px)'); - const { showToast } = useToastContext(); - const queryClient = useQueryClient(); - const [isOpen, setIsOpen] = useState(false); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); - const [deleteRow, setDeleteRow] = useState(null); - const [unarchivingId, setUnarchivingId] = useState(null); - - const [queryParams, setQueryParams] = useState(DEFAULT_PARAMS); - const [sorting, setSorting] = useState(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, unknown>[] = useMemo( - () => [ - { - accessorKey: 'title', - accessorFn: (row: Record): unknown => { - const convo = row as TConversation; - return convo.title; - }, - header: () => ( - - {localize('com_nav_archive_name')} - - ), - cell: ({ row }) => { - const convo = row.original as TConversation; - const { conversationId, title } = convo; - return ( - - ); - }, - meta: { - className: 'min-w-[150px] flex-1', - isRowHeader: true, - }, - enableSorting: true, - }, - { - accessorKey: 'createdAt', - accessorFn: (row: Record): unknown => { - const convo = row as TConversation; - return convo.createdAt; - }, - header: () => ( - - {localize('com_nav_archive_created_at')} - - ), - 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: () => ( - - {localize('com_assistants_actions')} - - ), - cell: ({ row }) => { - const convo = row.original as TConversation; - const { title } = convo; - const isRowUnarchiving = unarchivingId === convo.conversationId; - - return ( -
- { - const conversationId = convo.conversationId; - if (!conversationId) return; - handleUnarchive(conversationId); - }} - disabled={isRowUnarchiving} - aria-label={localize('com_ui_unarchive_conversation_title', { 0: title })} - > - {isRowUnarchiving ? : } - - } - /> - { - setDeleteRow(convo); - setIsDeleteOpen(true); - }} - aria-label={localize('com_ui_delete_conversation_title', { 0: title })} - > - - - } - /> -
- ); - }, - meta: { - className: 'w-24', - }, - enableSorting: false, - }, - ], - [isSmallScreen, localize, handleUnarchive, unarchivingId], - ); return (
- +
{localize('com_nav_archived_chats')}
- - - - {localize('com_nav_archived_chats')} - - - - - -
- -
-
- } - 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 ? : localize('com_ui_delete'), - }} + title={localize('com_nav_archived_chats')} + className="max-w-[1000px]" + showCancelButton={false} + main={} />
diff --git a/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx b/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx new file mode 100644 index 0000000000..d3e4ef22f5 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx @@ -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(DEFAULT_PARAMS); + const [deleteConversation, setDeleteConversation] = useState(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 ( + + ); + }, + cell: ({ row }) => { + const { conversationId, title } = row.original; + return ( + + ); + }, + meta: { + size: isSmallScreen ? '70%' : '50%', + mobileSize: '70%', + }, + }, + { + accessorKey: 'createdAt', + header: () => { + const isSorted = queryParams.sortBy === 'createdAt'; + const sortDirection = queryParams.sortDirection; + return ( + + ); + }, + cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen), + meta: { + size: isSmallScreen ? '30%' : '35%', + mobileSize: '30%', + }, + }, + { + accessorKey: 'actions', + header: () => ( + + ), + cell: ({ row }) => { + const conversation = row.original; + return ( +
+ + unarchiveMutation.mutate({ + conversationId: conversation.conversationId, + isArchived: false, + }) + } + title={localize('com_ui_unarchive')} + aria-label={localize('com_ui_unarchive')} + disabled={unarchiveMutation.isLoading} + > + {unarchiveMutation.isLoading ? ( + + ) : ( + + )} + + } + /> + { + setDeleteConversation(row.original); + setIsDeleteOpen(true); + }} + title={localize('com_ui_delete')} + aria-label={localize('com_ui_delete')} + > + + + } + /> +
+ ); + }, + meta: { + size: '15%', + mobileSize: '25%', + }, + }, + ], + [handleSort, isSmallScreen, localize, queryParams, unarchiveMutation], + ); + + return ( + <> + + + + + + + }} + /> + + +
+ + +
+
+
+ + ); +} diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 87d6e0aafa..d61a02b94a 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -396,7 +396,6 @@ "com_nav_archive_created_at": "Date Archived", "com_nav_archive_name": "Name", "com_nav_archived_chats": "Archived chats", - "com_ui_manage_archived_chats": "Manage archived chats", "com_nav_at_command": "@-Command", "com_nav_at_command_description": "Toggle command \"@\" for switching endpoints, models, presets, etc.", "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_heading": "Delete Shared Link", "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_tool": "Delete 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_var": "Share {{0}}", "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_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_shop": "Shopping", "com_ui_show_all": "Show All", @@ -1400,8 +1394,5 @@ "com_ui_zoom_in": "Zoom in", "com_ui_zoom_level": "Zoom level", "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" } diff --git a/package-lock.json b/package-lock.json index 779c5ddcbd..43ac2ab132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48832,7 +48832,6 @@ "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^15.0.0", "@rollup/plugin-replace": "^5.0.5", - "@rollup/plugin-terser": "^0.4.4", "@tanstack/react-query": "^4.28.0", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.13.13", diff --git a/packages/client/package.json b/packages/client/package.json index a2c12987a0..cee38eebed 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -23,6 +23,7 @@ "clean": "rimraf dist", "b:clean": "bun run rimraf dist", "build": "npm run clean && rollup -c --bundleConfigAsCjs", + "build:dev": "npm run clean && NODE_ENV=development rollup -c --bundleConfigAsCjs", "b:build": "bun run b:clean && bun run rollup -c --silent --bundleConfigAsCjs", "build:watch": "rollup -c -w --bundleConfigAsCjs", "dev": "rollup -c -w --bundleConfigAsCjs" @@ -83,7 +84,6 @@ "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^15.0.0", "@rollup/plugin-replace": "^5.0.5", - "@rollup/plugin-terser": "^0.4.4", "@tanstack/react-query": "^4.28.0", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.13.13", diff --git a/packages/client/rollup.config.js b/packages/client/rollup.config.js index 11ae66567c..149545f626 100644 --- a/packages/client/rollup.config.js +++ b/packages/client/rollup.config.js @@ -1,7 +1,6 @@ // ESM bundler config for React components import { fileURLToPath } from 'url'; import alias from '@rollup/plugin-alias'; -import terser from '@rollup/plugin-terser'; import postcss from 'rollup-plugin-postcss'; import replace from '@rollup/plugin-replace'; import commonjs from '@rollup/plugin-commonjs'; @@ -46,26 +45,23 @@ const plugins = [ clean: true, check: false, }), - terser({ - compress: { - directives: false, - }, - }), ]; +const isDev = process.env.NODE_ENV !== 'production'; + export default { input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', - sourcemap: true, + sourcemap: isDev ? 'inline' : true, exports: 'named', }, { file: pkg.module, format: 'esm', - sourcemap: true, + sourcemap: isDev ? 'inline' : true, exports: 'named', }, ], diff --git a/packages/client/src/components/index.ts b/packages/client/src/components/index.ts index 5f37ae25fc..b4d071e732 100644 --- a/packages/client/src/components/index.ts +++ b/packages/client/src/components/index.ts @@ -30,7 +30,7 @@ export * from './InputOTP'; export * from './MultiSearch'; export * from './Resizable'; export * from './Select'; -export * from './DataTable/index'; +export { default as DataTable } from './DataTable'; export { default as Radio } from './Radio'; export { default as Badge } from './Badge'; export { default as Avatar } from './Avatar';