diff --git a/api/models/Share.js b/api/models/Share.js new file mode 100644 index 00000000000..f190c8b08a8 --- /dev/null +++ b/api/models/Share.js @@ -0,0 +1,91 @@ +const crypto = require('crypto'); +const { getMessages } = require('./Message'); +const SharedLink = require('./schema/shareSchema'); +const logger = require('~/config/winston'); + +module.exports = { + SharedLink, + getSharedMessages: async (shareId) => { + try { + const share = await SharedLink.findOne({ shareId }).populate('messages').lean(); + if (!share || !share.conversationId || !share.isPublic) { + return null; + } + + const anonymousMessage = share.messages.map((message) => { + if (share.isAnonymous) { + return { + ...message, + user: 'anonymous', + }; + } + return message; + }); + return { + ...share, + messages: anonymousMessage, + }; + } catch (error) { + logger.error('[getShare] Error getting share link', error); + return { message: 'Error getting share link' }; + } + }, + + getSharedLinks: async (user, pageNumber = 1, pageSize = 25, isPublic = true) => { + const query = { user, isPublic }; + try { + const totalConvos = (await SharedLink.countDocuments(query)) || 1; + const totalPages = Math.ceil(totalConvos / pageSize); + const shares = await SharedLink.find(query) + .sort({ updatedAt: -1 }) + .skip((pageNumber - 1) * pageSize) + .limit(pageSize) + .lean(); + return { sharedLinks: shares, pages: totalPages, pageNumber, pageSize }; + } catch (error) { + logger.error('[getShareByPage] Error getting shares', error); + return { message: 'Error getting shares' }; + } + }, + + createSharedLink: async (user, { conversationId, ...shareData }) => { + const share = await SharedLink.findOne({ conversationId }).lean(); + if (share) { + return share; + } + + try { + const shareId = crypto.randomUUID(); + const messages = await getMessages({ conversationId }); + const update = { ...shareData, shareId, messages, user }; + return await SharedLink.findOneAndUpdate({ conversationId: conversationId, user }, update, { + new: true, + upsert: true, + }); + } catch (error) { + logger.error('[saveShareMessage] Error saving conversation', error); + return { message: 'Error saving conversation' }; + } + }, + updateSharedLink: async (user, { conversationId, ...shareData }) => { + const share = await SharedLink.findOne({ conversationId }).lean(); + if (!share) { + return { message: 'Share not found' }; + } + // update messages to the latest + const messages = await getMessages({ conversationId }); + const update = { ...shareData, messages, user }; + return await SharedLink.findOneAndUpdate({ conversationId: conversationId, user }, update, { + new: true, + upsert: false, + }); + }, + + deleteSharedLink: async (user, { shareId }) => { + const share = await SharedLink.findOne({ shareId, user }); + if (!share) { + return { message: 'Share not found' }; + } + return await SharedLink.findOneAndDelete({ shareId, user }); + }, +}; diff --git a/api/models/schema/shareSchema.js b/api/models/schema/shareSchema.js new file mode 100644 index 00000000000..56ecec00c0d --- /dev/null +++ b/api/models/schema/shareSchema.js @@ -0,0 +1,38 @@ +const mongoose = require('mongoose'); + +const shareSchema = mongoose.Schema( + { + conversationId: { + type: String, + required: true, + }, + title: { + type: String, + index: true, + }, + user: { + type: String, + index: true, + }, + messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }], + shareId: { + type: String, + index: true, + }, + isPublic: { + type: Boolean, + default: false, + }, + isVisible: { + type: Boolean, + default: false, + }, + isAnonymous: { + type: Boolean, + default: true, + }, + }, + { timestamps: true }, +); + +module.exports = mongoose.model('SharedLink', shareSchema); diff --git a/api/server/index.js b/api/server/index.js index 4e85c508010..f47af6725ac 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -85,6 +85,7 @@ const startServer = async () => { app.use('/api/assistants', routes.assistants); app.use('/api/files', await routes.files.initialize()); app.use('/images/', validateImageRequest, routes.staticRoute); + app.use('/api/share', routes.share); app.use((req, res) => { res.status(404).sendFile(path.join(app.locals.paths.dist, 'index.html')); diff --git a/api/server/routes/index.js b/api/server/routes/index.js index 8b1ffd8fe8c..958cf0aed52 100644 --- a/api/server/routes/index.js +++ b/api/server/routes/index.js @@ -18,6 +18,7 @@ const config = require('./config'); const assistants = require('./assistants'); const files = require('./files'); const staticRoute = require('./static'); +const share = require('./share'); module.exports = { search, @@ -40,4 +41,5 @@ module.exports = { assistants, files, staticRoute, + share, }; diff --git a/api/server/routes/share.js b/api/server/routes/share.js new file mode 100644 index 00000000000..7fbdae23f1d --- /dev/null +++ b/api/server/routes/share.js @@ -0,0 +1,75 @@ +const express = require('express'); + +const { + getSharedMessages, + createSharedLink, + updateSharedLink, + getSharedLinks, + deleteSharedLink, +} = require('~/models/Share'); +const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); +const router = express.Router(); + +/** + * Shared messages + * this route does not require authentication + */ +router.get('/:shareId', async (req, res) => { + const share = await getSharedMessages(req.params.shareId); + + if (share) { + res.status(200).json(share); + } else { + res.status(404).end(); + } +}); + +/** + * Shared links + */ +router.get('/', requireJwtAuth, async (req, res) => { + let pageNumber = req.query.pageNumber || 1; + pageNumber = parseInt(pageNumber, 10); + + if (isNaN(pageNumber) || pageNumber < 1) { + return res.status(400).json({ error: 'Invalid page number' }); + } + + let pageSize = req.query.pageSize || 25; + pageSize = parseInt(pageSize, 10); + + if (isNaN(pageSize) || pageSize < 1) { + return res.status(400).json({ error: 'Invalid page size' }); + } + const isPublic = req.query.isPublic === 'true'; + res.status(200).send(await getSharedLinks(req.user.id, pageNumber, pageSize, isPublic)); +}); + +router.post('/', requireJwtAuth, async (req, res) => { + const created = await createSharedLink(req.user.id, req.body); + if (created) { + res.status(200).json(created); + } else { + res.status(404).end(); + } +}); + +router.patch('/', requireJwtAuth, async (req, res) => { + const updated = await updateSharedLink(req.user.id, req.body); + if (updated) { + res.status(200).json(updated); + } else { + res.status(404).end(); + } +}); + +router.delete('/:shareId', requireJwtAuth, async (req, res) => { + const deleted = await deleteSharedLink(req.user.id, { shareId: req.params.shareId }); + if (deleted) { + res.status(200).json(deleted); + } else { + res.status(404).end(); + } +}); + +module.exports = router; diff --git a/client/src/components/Auth/BlinkAnimation.tsx b/client/src/components/Auth/BlinkAnimation.tsx new file mode 100644 index 00000000000..4323a3a6316 --- /dev/null +++ b/client/src/components/Auth/BlinkAnimation.tsx @@ -0,0 +1,29 @@ +export const BlinkAnimation = ({ + active, + children, +}: { + active: boolean; + children: React.ReactNode; +}) => { + const style = ` + @keyframes blink-animation { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } + }`; + + if (!active) { + return <>{children}; + } + + return ( + <> + +
{children}
+ + ); +}; diff --git a/client/src/components/Auth/Login.tsx b/client/src/components/Auth/Login.tsx index 8bff10c2588..124fc6a655b 100644 --- a/client/src/components/Auth/Login.tsx +++ b/client/src/components/Auth/Login.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useGetStartupConfig } from 'librechat-data-provider/react-query'; import { GoogleIcon, FacebookIcon, OpenIDIcon, GithubIcon, DiscordIcon } from '~/components'; @@ -8,10 +8,19 @@ import SocialButton from './SocialButton'; import { getLoginError } from '~/utils'; import { useLocalize } from '~/hooks'; import LoginForm from './LoginForm'; +import { BlinkAnimation } from './BlinkAnimation'; +import { TStartupConfig } from 'librechat-data-provider'; function Login() { const { login, error, isAuthenticated } = useAuthContext(); - const { data: startupConfig } = useGetStartupConfig(); + const [startupConfig, setStartupConfig] = useState(null); + const { + data, + isFetching, + error: startupConfigError, + } = useGetStartupConfig({ + enabled: startupConfig === null, + }); const localize = useLocalize(); const navigate = useNavigate(); @@ -19,16 +28,13 @@ function Login() { if (isAuthenticated) { navigate('/c/new', { replace: true }); } - }, [isAuthenticated, navigate]); - - if (!startupConfig) { - return null; - } - - const socialLogins = startupConfig.socialLogins ?? []; + if (data) { + setStartupConfig(data); + } + }, [isAuthenticated, navigate, data]); const providerComponents = { - discord: ( + discord: startupConfig?.discordLoginEnabled && ( ), - facebook: ( + facebook: startupConfig?.facebookLoginEnabled && ( ), - github: ( + github: startupConfig?.githubLoginEnabled && ( ), - google: ( + google: startupConfig?.googleLoginEnabled && ( ), - openid: ( + openid: startupConfig?.openidLoginEnabled && ( ); + const loginFormRender = startupConfig?.emailLoginEnabled && ; + const registrationRender = startupConfig?.registrationEnabled && ( +

+ {' '} + {localize('com_auth_no_account')}{' '} + + {localize('com_auth_sign_up')} + +

+ ); + + const socialLoginRender = startupConfig && startupConfig.socialLoginEnabled && ( + <> + {startupConfig.emailLoginEnabled && ( + <> +
+
+ Or +
+
+
+ + )} +
+ {startupConfig.socialLogins?.map((provider) => providerComponents[provider] || null)} +
+ + ); + + const errorRender = (errorMessage: string) => ( +
+ {errorMessage} +
+ ); + return (
-
- Logo -
+ +
+ Logo +
+
+ {startupConfigError && ( +
+ {errorRender(localize('com_auth_error_login_server'))} +
+ )}
+
-

- {localize('com_auth_welcome_back')} -

- {error && ( -
- {localize(getLoginError(error))} -
- )} - {startupConfig.emailLoginEnabled && } - {startupConfig.registrationEnabled && ( -

- {' '} - {localize('com_auth_no_account')}{' '} - - {localize('com_auth_sign_up')} - -

- )} - {startupConfig.socialLoginEnabled && ( - <> - {startupConfig.emailLoginEnabled && ( - <> -
-
- Or -
-
-
- - )} -
- {socialLogins.map((provider) => providerComponents[provider] || null)} -
- + {localize('com_auth_welcome_back')} + )} + {error && errorRender(localize(getLoginError(error)))} + {loginFormRender} + {registrationRender} + {socialLoginRender}
diff --git a/client/src/components/Chat/ExportAndShareMenu.tsx b/client/src/components/Chat/ExportAndShareMenu.tsx new file mode 100644 index 00000000000..67027cf6b03 --- /dev/null +++ b/client/src/components/Chat/ExportAndShareMenu.tsx @@ -0,0 +1,64 @@ +import { useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { TConversation } from 'librechat-data-provider'; + +import ExportButton from './ExportButton'; +import DropDownMenu from '../Conversations/DropDownMenu'; +import ShareButton from '../Conversations/ShareButton'; +import HoverToggle from '../Conversations/HoverToggle'; +import { useRecoilValue } from 'recoil'; +import store from '~/store'; +import { Download } from 'lucide-react'; + +export default function ExportAndShareMenu() { + const location = useLocation(); + + const activeConvo = useRecoilValue(store.conversationByIndex(0)); + const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation); + const [isPopoverActive, setIsPopoverActive] = useState(false); + let conversation: TConversation | null | undefined; + if (location.state?.from?.pathname.includes('/chat')) { + conversation = globalConvo; + } else { + conversation = activeConvo; + } + + const exportable = + conversation && + conversation.conversationId && + conversation.conversationId !== 'new' && + conversation.conversationId !== 'search'; + + if (!exportable) { + return <>; + } + + const isActiveConvo = exportable; + + return ( + + } + tooltip="Export/Share" + className="pointer-cursor relative z-50 flex h-[40px] min-w-4 flex-none flex-col items-center justify-center rounded-md border border-gray-100 bg-white px-3 text-left hover:bg-gray-50 focus:outline-none focus:ring-0 focus:ring-offset-0 radix-state-open:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:radix-state-open:bg-gray-700 sm:text-sm" + > + {conversation && conversation.conversationId && ( + <> + + + + )} + + + ); +} diff --git a/client/src/components/Chat/ExportButton.tsx b/client/src/components/Chat/ExportButton.tsx index 1c06b6960e5..31f59e8e7c1 100644 --- a/client/src/components/Chat/ExportButton.tsx +++ b/client/src/components/Chat/ExportButton.tsx @@ -1,68 +1,42 @@ import React from 'react'; import { useState } from 'react'; -import { useLocation } from 'react-router-dom'; import type { TConversation } from 'librechat-data-provider'; import { Upload } from 'lucide-react'; -import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui'; import { useLocalize } from '~/hooks'; import { ExportModal } from '../Nav'; -import { useRecoilValue } from 'recoil'; -import store from '~/store'; -function ExportButton() { +function ExportButton({ + conversation, + setPopoverActive, +}: { + conversation: TConversation; + setPopoverActive: (value: boolean) => void; +}) { const localize = useLocalize(); - const location = useLocation(); const [showExports, setShowExports] = useState(false); - const activeConvo = useRecoilValue(store.conversationByIndex(0)); - const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation); - - let conversation: TConversation | null | undefined; - if (location.state?.from?.pathname.includes('/chat')) { - conversation = globalConvo; - } else { - conversation = activeConvo; - } - const clickHandler = () => { - if (exportable) { - setShowExports(true); - } + setShowExports(true); }; - const exportable = - conversation && - conversation.conversationId && - conversation.conversationId !== 'new' && - conversation.conversationId !== 'search'; + const onOpenChange = (value: boolean) => { + setShowExports(value); + setPopoverActive(value); + }; return ( <> - {exportable && ( -
- - - - - - - {localize('com_nav_export_conversation')} - - - -
- )} + + {showExports && ( - + )} ); diff --git a/client/src/components/Chat/Footer.tsx b/client/src/components/Chat/Footer.tsx index f5ee1b1675b..cca7d9a7cf5 100644 --- a/client/src/components/Chat/Footer.tsx +++ b/client/src/components/Chat/Footer.tsx @@ -3,7 +3,7 @@ import { Constants } from 'librechat-data-provider'; import { useGetStartupConfig } from 'librechat-data-provider/react-query'; import { useLocalize } from '~/hooks'; -export default function Footer() { +export default function Footer({ className }: { className?: string }) { const { data: config } = useGetStartupConfig(); const localize = useLocalize(); @@ -52,7 +52,12 @@ export default function Footer() { ); return ( -
+
{footerElements.map((contentRender, index) => { const isLastElement = index === footerElements.length - 1; return ( diff --git a/client/src/components/Chat/Header.tsx b/client/src/components/Chat/Header.tsx index ab40ace0b04..0a0dcb166dc 100644 --- a/client/src/components/Chat/Header.tsx +++ b/client/src/components/Chat/Header.tsx @@ -6,6 +6,7 @@ import type { ContextType } from '~/common'; import { EndpointsMenu, ModelSpecsMenu, PresetsMenu, HeaderNewChat } from './Menus'; import HeaderOptions from './Input/HeaderOptions'; import ExportButton from './ExportButton'; +import ExportAndShareMenu from './ExportAndShareMenu'; const defaultInterface = getConfigDefaults().interface; @@ -28,7 +29,7 @@ export default function Header() { {} {interfaceConfig.presets && }
- +
{/* Empty div for spacing */}
diff --git a/client/src/components/Conversations/Convo.tsx b/client/src/components/Conversations/Convo.tsx index 34c006d6c47..efb1cb95c78 100644 --- a/client/src/components/Conversations/Convo.tsx +++ b/client/src/components/Conversations/Convo.tsx @@ -9,13 +9,14 @@ import { useConversations, useNavigateToConvo } from '~/hooks'; import { NotificationSeverity } from '~/common'; import { ArchiveIcon } from '~/components/svg'; import { useToastContext } from '~/Providers'; -import EditMenuButton from './EditMenuButton'; +import DropDownMenu from './DropDownMenu'; import ArchiveButton from './ArchiveButton'; import DeleteButton from './DeleteButton'; import RenameButton from './RenameButton'; import HoverToggle from './HoverToggle'; import { cn } from '~/utils'; import store from '~/store'; +import ShareButton from './ShareButton'; type KeyEvent = KeyboardEvent; @@ -124,7 +125,15 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa isPopoverActive={isPopoverActive} setIsPopoverActive={setIsPopoverActive} > - + + + - + = ({ children }: EditMenuButtonProps) => { +const DropDownMenu: FC = ({ + children, + icon = , + tooltip = 'More', + className, +}: DropDownMenuProps) => { const localize = useLocalize(); - const { setPopoverActive } = useToggle(); + const { isPopoverActive, setPopoverActive } = useToggle(); return ( - setPopoverActive(open)}> + setPopoverActive(open)}>
= ({ children }: EditMenuButtonPro - - {localize('com_ui_more_options')} + {tooltip} @@ -57,4 +68,4 @@ const EditMenuButton: FC = ({ children }: EditMenuButtonPro ); }; -export default EditMenuButton; +export default DropDownMenu; diff --git a/client/src/components/Conversations/HoverToggle.tsx b/client/src/components/Conversations/HoverToggle.tsx index fab8bb2a262..a58fba32a2e 100644 --- a/client/src/components/Conversations/HoverToggle.tsx +++ b/client/src/components/Conversations/HoverToggle.tsx @@ -15,7 +15,7 @@ const HoverToggle = ({ }) => { const setPopoverActive = (value: boolean) => setIsPopoverActive(value); return ( - +
void; +}) { + const localize = useLocalize(); + const [share, setShare] = useState(null); + const [open, setOpen] = useState(false); + const [isUpdated, setIsUpdated] = useState(false); + + const classProp: { className?: string } = { + className: 'p-1 hover:text-black dark:hover:text-white', + }; + if (className) { + classProp.className = className; + } + const renderShareButton = () => { + if (appendLabel) { + return ( + <> + {localize('com_ui_share')} + + ); + } + return ( + + + + + + + + + {localize('com_ui_share')} + + + + ); + }; + + const buttons = share && ( + + ); + + const onOpenChange = (open: boolean) => { + setPopoverActive(open); + setOpen(open); + }; + return ( + + + + + + + + } + /> + + ); +} diff --git a/client/src/components/Conversations/ShareDialog.tsx b/client/src/components/Conversations/ShareDialog.tsx new file mode 100644 index 00000000000..a9368736e86 --- /dev/null +++ b/client/src/components/Conversations/ShareDialog.tsx @@ -0,0 +1,84 @@ +import { useLocalize } from '~/hooks'; + +import { useCreateSharedLinkMutation } from '~/data-provider'; +import { useEffect, useState } from 'react'; +import { TSharedLink } from 'librechat-data-provider'; +import { useToastContext } from '~/Providers'; +import { NotificationSeverity } from '~/common'; +import { Spinner } from '~/components/svg'; + +export default function ShareDialog({ + conversationId, + title, + share, + setShare, + setDialogOpen, + isUpdated, +}: { + conversationId: string; + title: string; + share: TSharedLink | null; + setShare: (share: TSharedLink | null) => void; + setDialogOpen: (open: boolean) => void; + isUpdated: boolean; +}) { + const localize = useLocalize(); + const { showToast } = useToastContext(); + const { mutate, isLoading } = useCreateSharedLinkMutation(); + const [isNewSharedLink, setIsNewSharedLink] = useState(false); + + useEffect(() => { + if (isLoading || share) { + return; + } + const data = { + conversationId, + title, + isAnonymous: true, + }; + + mutate(data, { + onSuccess: (result) => { + setShare(result); + setIsNewSharedLink(!result.isPublic); + }, + onError: () => { + showToast({ + message: localize('com_ui_share_error'), + severity: NotificationSeverity.ERROR, + showIcon: true, + }); + setDialogOpen(false); + }, + }); + + // mutation.mutate should only be called once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+
+ {isUpdated ? ( + isNewSharedLink ? ( + localize('com_ui_share_created_message') + ) : ( + <>{localize('com_ui_share_updated_message')} + ) + ) : share?.isPublic ? ( + localize('com_ui_share_update_message') + ) : ( + localize('com_ui_share_create_message') + )} +
+
+ ); +} diff --git a/client/src/components/Conversations/SharedLinkButton.tsx b/client/src/components/Conversations/SharedLinkButton.tsx new file mode 100644 index 00000000000..0d54e3d8c82 --- /dev/null +++ b/client/src/components/Conversations/SharedLinkButton.tsx @@ -0,0 +1,114 @@ +import copy from 'copy-to-clipboard'; +import { Copy, Link } from 'lucide-react'; +import { Button } from '~/components/ui'; +import { Spinner } from '~/components/svg'; +import { useState } from 'react'; +import { useUpdateSharedLinkMutation } from '~/data-provider'; +import { TSharedLink } from 'librechat-data-provider'; + +export default function SharedLinkButton({ + conversationId, + share, + setShare, + isUpdated, + setIsUpdated, +}: { + conversationId: string; + share: TSharedLink; + setShare: (share: TSharedLink) => void; + isUpdated: boolean; + setIsUpdated: (isUpdated: boolean) => void; +}) { + const [isCopying, setIsCopying] = useState(false); + + const { mutateAsync, isLoading } = useUpdateSharedLinkMutation(); + + const copyLink = () => { + if (!share) { + return; + } + setIsCopying(true); + const sharedLink = + window.location.protocol + '//' + window.location.host + '/share/' + share.shareId; + copy(sharedLink); + setTimeout(() => { + setIsCopying(false); + }, 1500); + }; + const updateSharedLink = async () => { + if (!share) { + return; + } + const result = await mutateAsync({ + shareId: share.shareId, + conversationId: conversationId, + isPublic: true, + isVisible: true, + isAnonymous: true, + }); + + if (result) { + setShare(result); + setIsUpdated(true); + copyLink(); + } + }; + const getHandler = () => { + if (isUpdated) { + return { + handler: () => { + copyLink(); + }, + label: ( + <> + + Copy link + + ), + }; + } + if (share?.isPublic) { + return { + handler: async () => { + await updateSharedLink(); + }, + + label: ( + <> + + Update link + + ), + }; + } + return { + handler: updateSharedLink, + label: ( + <> + + Create link + + ), + }; + }; + + const handlers = getHandler(); + return ( + + ); +} diff --git a/client/src/components/Conversations/ToggleContext.ts b/client/src/components/Conversations/ToggleContext.ts index 75276dfabdd..af85b85149c 100644 --- a/client/src/components/Conversations/ToggleContext.ts +++ b/client/src/components/Conversations/ToggleContext.ts @@ -3,6 +3,7 @@ import { createContext, useContext } from 'react'; const defaultFunction: (value: boolean) => void = () => ({}); export const ToggleContext = createContext({ setPopoverActive: defaultFunction, + isPopoverActive: false, }); export const useToggle = () => useContext(ToggleContext); diff --git a/client/src/components/Endpoints/Icon.tsx b/client/src/components/Endpoints/Icon.tsx index eac5d60ceac..1f5bd17d7f6 100644 --- a/client/src/components/Endpoints/Icon.tsx +++ b/client/src/components/Endpoints/Icon.tsx @@ -1,36 +1,14 @@ -import { EModelEndpoint } from 'librechat-data-provider'; -import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon'; -import { - Plugin, - GPTIcon, - UserIcon, - PaLMIcon, - CodeyIcon, - GeminiIcon, - AssistantIcon, - AnthropicIcon, - AzureMinimalIcon, - CustomMinimalIcon, -} from '~/components/svg'; +import { UserIcon } from '~/components/svg'; import { useAuthContext } from '~/hooks/AuthContext'; import useAvatar from '~/hooks/Messages/useAvatar'; import useLocalize from '~/hooks/useLocalize'; import { IconProps } from '~/common'; import { cn } from '~/utils'; +import MessageEndpointIcon from './MessageEndpointIcon'; const Icon: React.FC = (props) => { const { user } = useAuthContext(); - const { - error, - button, - iconURL, - endpoint, - jailbreak, - size = 30, - model = '', - assistantName, - isCreatedByUser, - } = props; + const { size = 30, isCreatedByUser } = props; const avatarSrc = useAvatar(user); const localize = useLocalize(); @@ -65,141 +43,7 @@ const Icon: React.FC = (props) => {
); } - - const endpointIcons = { - [EModelEndpoint.assistants]: { - icon: props.iconURL ? ( -
-
- {assistantName} -
-
- ) : ( -
-
- -
-
- ), - name: endpoint, - }, - [EModelEndpoint.azureOpenAI]: { - icon: , - bg: 'linear-gradient(0.375turn, #61bde2, #4389d0)', - name: 'ChatGPT', - }, - [EModelEndpoint.openAI]: { - icon: , - bg: - typeof model === 'string' && model.toLowerCase().includes('gpt-4') ? '#AB68FF' : '#19C37D', - name: 'ChatGPT', - }, - [EModelEndpoint.gptPlugins]: { - icon: , - bg: `rgba(69, 89, 164, ${button ? 0.75 : 1})`, - name: 'Plugins', - }, - [EModelEndpoint.google]: { - icon: model?.toLowerCase()?.includes('code') ? ( - - ) : model?.toLowerCase()?.includes('gemini') ? ( - - ) : ( - - ), - name: model?.toLowerCase()?.includes('code') - ? 'Codey' - : model?.toLowerCase()?.includes('gemini') - ? 'Gemini' - : 'PaLM2', - }, - [EModelEndpoint.anthropic]: { - icon: , - bg: '#d09a74', - name: 'Claude', - }, - [EModelEndpoint.bingAI]: { - icon: jailbreak ? ( - Bing Icon - ) : ( - Sydney Icon - ), - name: jailbreak ? 'Sydney' : 'BingAI', - }, - [EModelEndpoint.chatGPTBrowser]: { - icon: , - bg: - typeof model === 'string' && model.toLowerCase().includes('gpt-4') - ? '#AB68FF' - : `rgba(0, 163, 255, ${button ? 0.75 : 1})`, - name: 'ChatGPT', - }, - [EModelEndpoint.custom]: { - icon: , - name: 'Custom', - }, - null: { icon: , bg: 'grey', name: 'N/A' }, - default: { - icon: ( -
-
- -
-
- ), - name: endpoint, - }, - }; - - let { icon, bg, name } = - endpoint && endpointIcons[endpoint] ? endpointIcons[endpoint] : endpointIcons.default; - - if (iconURL && endpointIcons[iconURL]) { - ({ icon, bg, name } = endpointIcons[iconURL]); - } - - if (endpoint === EModelEndpoint.assistants) { - return icon; - } - - return ( -
- {icon} - {error && ( - - ! - - )} -
- ); + return ; }; export default Icon; diff --git a/client/src/components/Endpoints/MessageEndpointIcon.tsx b/client/src/components/Endpoints/MessageEndpointIcon.tsx new file mode 100644 index 00000000000..43ea9e2e6a4 --- /dev/null +++ b/client/src/components/Endpoints/MessageEndpointIcon.tsx @@ -0,0 +1,166 @@ +import { EModelEndpoint } from 'librechat-data-provider'; +import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon'; +import { + Plugin, + GPTIcon, + PaLMIcon, + CodeyIcon, + GeminiIcon, + AssistantIcon, + AnthropicIcon, + AzureMinimalIcon, + CustomMinimalIcon, +} from '~/components/svg'; + +import { IconProps } from '~/common'; +import { cn } from '~/utils'; + +const MessageEndpointIcon: React.FC = (props) => { + const { + error, + button, + iconURL, + endpoint, + jailbreak, + size = 30, + model = '', + assistantName, + } = props; + + const endpointIcons = { + [EModelEndpoint.assistants]: { + icon: props.iconURL ? ( +
+
+ {assistantName} +
+
+ ) : ( +
+
+ +
+
+ ), + name: endpoint, + }, + [EModelEndpoint.azureOpenAI]: { + icon: , + bg: 'linear-gradient(0.375turn, #61bde2, #4389d0)', + name: 'ChatGPT', + }, + [EModelEndpoint.openAI]: { + icon: , + bg: + typeof model === 'string' && model.toLowerCase().includes('gpt-4') ? '#AB68FF' : '#19C37D', + name: 'ChatGPT', + }, + [EModelEndpoint.gptPlugins]: { + icon: , + bg: `rgba(69, 89, 164, ${button ? 0.75 : 1})`, + name: 'Plugins', + }, + [EModelEndpoint.google]: { + icon: model?.toLowerCase()?.includes('code') ? ( + + ) : model?.toLowerCase()?.includes('gemini') ? ( + + ) : ( + + ), + name: model?.toLowerCase()?.includes('code') + ? 'Codey' + : model?.toLowerCase()?.includes('gemini') + ? 'Gemini' + : 'PaLM2', + }, + [EModelEndpoint.anthropic]: { + icon: , + bg: '#d09a74', + name: 'Claude', + }, + [EModelEndpoint.bingAI]: { + icon: jailbreak ? ( + Bing Icon + ) : ( + Sydney Icon + ), + name: jailbreak ? 'Sydney' : 'BingAI', + }, + [EModelEndpoint.chatGPTBrowser]: { + icon: , + bg: + typeof model === 'string' && model.toLowerCase().includes('gpt-4') + ? '#AB68FF' + : `rgba(0, 163, 255, ${button ? 0.75 : 1})`, + name: 'ChatGPT', + }, + [EModelEndpoint.custom]: { + icon: , + name: 'Custom', + }, + null: { icon: , bg: 'grey', name: 'N/A' }, + default: { + icon: ( +
+
+ +
+
+ ), + name: endpoint, + }, + }; + + let { icon, bg, name } = + endpoint && endpointIcons[endpoint] ? endpointIcons[endpoint] : endpointIcons.default; + + if (iconURL && endpointIcons[iconURL]) { + ({ icon, bg, name } = endpointIcons[iconURL]); + } + + if (endpoint === EModelEndpoint.assistants) { + return icon; + } + + return ( +
+ {icon} + {error && ( + + ! + + )} +
+ ); +}; + +export default MessageEndpointIcon; diff --git a/client/src/components/Nav/Nav.tsx b/client/src/components/Nav/Nav.tsx index e9d7c66b96e..53fc727e774 100644 --- a/client/src/components/Nav/Nav.tsx +++ b/client/src/components/Nav/Nav.tsx @@ -19,6 +19,7 @@ import NavToggle from './NavToggle'; import NavLinks from './NavLinks'; import NewChat from './NewChat'; import { cn } from '~/utils'; +import { ConversationListResponse } from 'librechat-data-provider'; import store from '~/store'; const Nav = ({ navVisible, setNavVisible }) => { @@ -59,7 +60,7 @@ const Nav = ({ navVisible, setNavVisible }) => { { enabled: isAuthenticated }, ); - const { containerRef, moveToTop } = useNavScrolling({ + const { containerRef, moveToTop } = useNavScrolling({ setShowLoading, hasNextPage: searchQuery ? searchQueryRes.hasNextPage : hasNextPage, fetchNextPage: searchQuery ? searchQueryRes.fetchNextPage : fetchNextPage, diff --git a/client/src/components/Nav/SettingsTabs/Data/Data.tsx b/client/src/components/Nav/SettingsTabs/Data/Data.tsx index 65704d8c126..6e8b8c0c185 100644 --- a/client/src/components/Nav/SettingsTabs/Data/Data.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/Data.tsx @@ -10,6 +10,7 @@ import { useConversation, useConversations, useOnClickOutside } from '~/hooks'; import ImportConversations from './ImportConversations'; import { ClearChatsButton } from './ClearChats'; import DangerButton from '../DangerButton'; +import SharedLinks from './SharedLinks'; export const RevokeKeysButton = ({ showText = true, @@ -107,6 +108,9 @@ function Data() {
+
+ +
diff --git a/client/src/components/Nav/SettingsTabs/Data/SharedLinkTable.tsx b/client/src/components/Nav/SettingsTabs/Data/SharedLinkTable.tsx new file mode 100644 index 00000000000..1984b4628b0 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Data/SharedLinkTable.tsx @@ -0,0 +1,178 @@ +import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks'; +import { MessageSquare, Link as LinkIcon } from 'lucide-react'; +import { useMemo, useState, MouseEvent } from 'react'; +import { useDeleteSharedLinkMutation, useSharedLinksInfiniteQuery } from '~/data-provider'; + +import { cn } from '~/utils'; +import { + Spinner, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, + TrashIcon, +} from '~/components'; +import { SharedLinksResponse, TSharedLink } from 'librechat-data-provider'; +import { Link } from 'react-router-dom'; + +function SharedLinkDeleteButton({ + shareId, + setIsDeleting, +}: { + shareId: string; + setIsDeleting: (isDeleting: boolean) => void; +}) { + const localize = useLocalize(); + const mutation = useDeleteSharedLinkMutation(); + + const handleDelete = async (e: MouseEvent) => { + e.preventDefault(); + if (mutation.isLoading) { + return; + } + setIsDeleting(true); + await mutation.mutateAsync({ shareId }); + setIsDeleting(false); + }; + return ( + + + + + + + + + {localize('com_ui_delete')} + + + + ); +} +function SourceChatButton({ conversationId }: { conversationId: string }) { + const localize = useLocalize(); + + return ( + + + + + + + + + {localize('com_nav_source_chat')} + + + + ); +} + +function ShareLinkRow({ sharedLink }: { sharedLink: TSharedLink }) { + const [isDeleting, setIsDeleting] = useState(false); + + return ( + + + + + {sharedLink.title} + + + +
+
+ {new Date(sharedLink.createdAt).toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + })} +
+
+ {sharedLink.conversationId && ( + <> + +
+ +
+ + )} +
+
+ + + ); +} +export default function ShareLinkTable({ className }: { className?: string }) { + const localize = useLocalize(); + const { isAuthenticated } = useAuthContext(); + const [showLoading, setShowLoading] = useState(false); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useSharedLinksInfiniteQuery( + { pageNumber: '1', isPublic: true }, + { enabled: isAuthenticated }, + ); + + const { containerRef } = useNavScrolling({ + setShowLoading, + hasNextPage: hasNextPage, + fetchNextPage: fetchNextPage, + isFetchingNextPage: isFetchingNextPage, + }); + + const sharedLinks = useMemo(() => data?.pages.flatMap((page) => page.sharedLinks) || [], [data]); + const classProp: { className?: string } = { + className: 'p-1 hover:text-black dark:hover:text-white', + }; + if (className) { + classProp.className = className; + } + + if (!sharedLinks || sharedLinks.length === 0) { + return
{localize('com_nav_shared_links_empty')}
; + } + + return ( +
+ + + + + + + + + {sharedLinks.map((sharedLink) => ( + + ))} + +
{localize('com_nav_shared_links_name')}{localize('com_nav_shared_links_date_shared')}
+ {(isFetchingNextPage || showLoading) && ( + + )} +
+ ); +} diff --git a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx new file mode 100644 index 00000000000..f09384b30cd --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx @@ -0,0 +1,29 @@ +import { useLocalize } from '~/hooks'; +import { Dialog, DialogTrigger } from '~/components/ui'; +import DialogTemplate from '~/components/ui/DialogTemplate'; + +import ShareLinkTable from './SharedLinkTable'; + +export default function SharedLinks() { + const localize = useLocalize(); + + return ( +
+
{localize('com_nav_shared_links')}
+ + + + + + } + /> + +
+ ); +} diff --git a/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx b/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx index cff93b92eb2..4425cd203ee 100644 --- a/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx +++ b/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx @@ -6,6 +6,7 @@ import ArchiveButton from '~/components/Conversations/ArchiveButton'; import DeleteButton from '~/components/Conversations/DeleteButton'; import { Spinner } from '~/components/svg'; import { cn } from '~/utils'; +import { ConversationListResponse } from 'librechat-data-provider'; export default function ArchivedChatsTable({ className }: { className?: string }) { const localize = useLocalize(); @@ -17,7 +18,7 @@ export default function ArchivedChatsTable({ className }: { className?: string } { enabled: isAuthenticated }, ); - const { containerRef, moveToTop } = useNavScrolling({ + const { containerRef, moveToTop } = useNavScrolling({ setShowLoading, hasNextPage: hasNextPage, fetchNextPage: fetchNextPage, diff --git a/client/src/components/Share/Message.tsx b/client/src/components/Share/Message.tsx new file mode 100644 index 00000000000..4b2dbba633a --- /dev/null +++ b/client/src/components/Share/Message.tsx @@ -0,0 +1,95 @@ +import type { TMessageProps } from '~/common'; +import { Plugin } from '~/components/Messages/Content'; +import MessageContent from '~/components/Chat/Messages/Content/MessageContent'; + +// eslint-disable-next-line import/no-cycle +import MultiMessage from './MultiMessage'; + +import { cn } from '~/utils'; + +import Icon from './MessageIcon'; +export default function Message(props: TMessageProps) { + const { + message, + conversation, + siblingIdx, + + setSiblingIdx, + currentEditId, + setCurrentEditId, + } = props; + + if (!message) { + return null; + } + + const { text, children, messageId = null, isCreatedByUser, error, unfinished } = message ?? {}; + + let messageLabel = ''; + if (isCreatedByUser) { + messageLabel = 'anonymous'; + } else { + messageLabel = message.sender; + } + + return ( + <> +
+
+
+
+
+
+
+ +
+
+
+
+
+
{messageLabel}
+
+
+ {/* Legacy Plugins */} + {message?.plugin && } + { + return; + }} + ask={() => { + return; + }} + text={text ?? ''} + message={message} + isCreatedByUser={isCreatedByUser ?? true} + siblingIdx={siblingIdx ?? 0} + setSiblingIdx={ + setSiblingIdx ?? + (() => { + return; + }) + } + /> +
+
+
+
+
+
+ + + ); +} diff --git a/client/src/components/Share/MessageIcon.tsx b/client/src/components/Share/MessageIcon.tsx new file mode 100644 index 00000000000..a16e0e8b72f --- /dev/null +++ b/client/src/components/Share/MessageIcon.tsx @@ -0,0 +1,75 @@ +import { useMemo } from 'react'; +import { useGetEndpointsQuery } from 'librechat-data-provider/react-query'; +import type { TMessage, TPreset, Assistant } from 'librechat-data-provider'; +import type { TMessageProps } from '~/common'; +import ConvoIconURL from '~/components/Endpoints/ConvoIconURL'; +import { getEndpointField, getIconEndpoint } from '~/utils'; +import { UserIcon } from '../svg'; +import MessageEndpointIcon from '../Endpoints/MessageEndpointIcon'; + +export default function MessageIcon( + props: Pick & { + assistant?: false | Assistant; + }, +) { + const { data: endpointsConfig } = useGetEndpointsQuery(); + const { message, conversation, assistant } = props; + + const assistantName = assistant ? (assistant.name as string | undefined) : ''; + const assistantAvatar = assistant ? (assistant.metadata?.avatar as string | undefined) : ''; + + const messageSettings = useMemo( + () => ({ + ...(conversation ?? {}), + ...({ + ...message, + iconURL: message?.iconURL ?? '', + } as TMessage), + }), + [conversation, message], + ); + + const iconURL = messageSettings?.iconURL; + let endpoint = messageSettings?.endpoint; + endpoint = getIconEndpoint({ endpointsConfig, iconURL, endpoint }); + const endpointIconURL = getEndpointField(endpointsConfig, endpoint, 'iconURL'); + + if (!message?.isCreatedByUser && iconURL && iconURL.includes('http')) { + return ( + + ); + } + + if (message?.isCreatedByUser) { + return ( +
+ +
+ ); + } + + return ( + + ); +} diff --git a/client/src/components/Share/MessagesView.tsx b/client/src/components/Share/MessagesView.tsx new file mode 100644 index 00000000000..d5c31281d2f --- /dev/null +++ b/client/src/components/Share/MessagesView.tsx @@ -0,0 +1,48 @@ +import { useState } from 'react'; +import type { TMessage } from 'librechat-data-provider'; +import MultiMessage from './MultiMessage'; + +export default function MessagesView({ + messagesTree: _messagesTree, + conversationId, +}: { + messagesTree?: TMessage[] | null; + conversationId: string; +}) { + const [currentEditId, setCurrentEditId] = useState(-1); + console.log(_messagesTree); + return ( +
+
+
+
+ {(_messagesTree && _messagesTree?.length == 0) || _messagesTree === null ? ( +
+ Nothing found +
+ ) : ( + <> +
+ +
+ + )} +
+
+
+
+
+ ); +} diff --git a/client/src/components/Share/MultiMessage.tsx b/client/src/components/Share/MultiMessage.tsx new file mode 100644 index 00000000000..a62e9d384f5 --- /dev/null +++ b/client/src/components/Share/MultiMessage.tsx @@ -0,0 +1,54 @@ +import { useEffect } from 'react'; +import { useRecoilState } from 'recoil'; +import type { TMessageProps } from '~/common'; +// eslint-disable-next-line import/no-cycle +import Message from './Message'; +import store from '~/store'; + +export default function MultiMessage({ + // messageId is used recursively here + messageId, + messagesTree, + currentEditId, + setCurrentEditId, +}: TMessageProps) { + const [siblingIdx, setSiblingIdx] = useRecoilState(store.messagesSiblingIdxFamily(messageId)); + + const setSiblingIdxRev = (value: number) => { + setSiblingIdx((messagesTree?.length ?? 0) - value - 1); + }; + + useEffect(() => { + // reset siblingIdx when the tree changes, mostly when a new message is submitting. + setSiblingIdx(0); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [messagesTree?.length]); + + useEffect(() => { + if (messagesTree?.length && siblingIdx >= messagesTree?.length) { + setSiblingIdx(0); + } + }, [siblingIdx, messagesTree?.length, setSiblingIdx]); + + if (!(messagesTree && messagesTree?.length)) { + return null; + } + + const message = messagesTree[messagesTree.length - siblingIdx - 1]; + + if (!message) { + return null; + } + + return ( + + ); +} diff --git a/client/src/components/Share/ShareView.tsx b/client/src/components/Share/ShareView.tsx new file mode 100644 index 00000000000..d34fe43e91e --- /dev/null +++ b/client/src/components/Share/ShareView.tsx @@ -0,0 +1,62 @@ +import { memo } from 'react'; + +import { useParams } from 'react-router-dom'; +import { useGetSharedMessages } from 'librechat-data-provider/react-query'; +import MessagesView from './MessagesView'; + +import { Spinner } from '~/components/svg'; + +import { buildTree } from '~/utils'; +import Footer from '../Chat/Footer'; +import { useLocalize } from '~/hooks'; + +function SharedView() { + const { shareId } = useParams(); + const localize = useLocalize(); + + const { data, isLoading } = useGetSharedMessages(shareId ?? ''); + + const dataTree = data && buildTree({ messages: data.messages }); + const messagesTree = dataTree?.length === 0 ? null : dataTree ?? null; + + return ( +
+
+
+ {isLoading ? ( +
+ +
+ ) : data && messagesTree && messagesTree.length !== 0 ? ( + <> +
+

{data.title}

+
+ {new Date(data.createdAt).toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + })} +
+
+ + + + ) : ( +
+ {localize('com_ui_shared_link_not_found')} +
+ )} +
+
+
+
+
+
+ ); +} + +export default memo(SharedView); diff --git a/client/src/data-provider/mutations.ts b/client/src/data-provider/mutations.ts index ade95fb9022..436914706f0 100644 --- a/client/src/data-provider/mutations.ts +++ b/client/src/data-provider/mutations.ts @@ -7,6 +7,8 @@ import { updateConversation, deleteConversation, updateConvoFields, + deleteSharedLink, + addSharedLink, } from '~/utils'; import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider'; import { useSetRecoilState } from 'recoil'; @@ -117,6 +119,96 @@ export const useArchiveConversationMutation = ( ); }; +export const useCreateSharedLinkMutation = ( + options?: t.CreateSharedLinkOptions, +): UseMutationResult => { + const queryClient = useQueryClient(); + const { onSuccess, ..._options } = options || {}; + return useMutation((payload: t.TSharedLinkRequest) => dataService.createSharedLink(payload), { + onSuccess: (_data, vars, context) => { + if (!vars.conversationId) { + return; + } + + queryClient.setQueryData([QueryKeys.sharedLinks], (sharedLink) => { + if (!sharedLink) { + return sharedLink; + } + + // If the shared link is public, add it to the shared links cache list + if (vars.isPublic) { + return addSharedLink(sharedLink, _data); + } else { + return deleteSharedLink(sharedLink, _data.shareId); + } + }); + + queryClient.setQueryData([QueryKeys.sharedLinks, _data.shareId], _data); + + onSuccess?.(_data, vars, context); + }, + ...(_options || {}), + }); +}; + +export const useUpdateSharedLinkMutation = ( + options?: t.UpdateSharedLinkOptions, +): UseMutationResult => { + const queryClient = useQueryClient(); + const { onSuccess, ..._options } = options || {}; + return useMutation((payload: t.TSharedLinkRequest) => dataService.updateSharedLink(payload), { + onSuccess: (_data, vars, context) => { + if (!vars.conversationId) { + return; + } + + queryClient.setQueryData([QueryKeys.sharedLinks], (sharedLink) => { + if (!sharedLink) { + return sharedLink; + } + + // If the shared link is public, add it to the shared links cache list. + if (vars.isPublic) { + // Even if the SharedLink data exists in the database, it is not registered in the cache when isPublic is false. + // Therefore, when isPublic is true, use addSharedLink instead of updateSharedLink. + return addSharedLink(sharedLink, _data); + } else { + return deleteSharedLink(sharedLink, _data.shareId); + } + }); + + queryClient.setQueryData([QueryKeys.sharedLinks, _data.shareId], _data); + + onSuccess?.(_data, vars, context); + }, + ...(_options || {}), + }); +}; + +export const useDeleteSharedLinkMutation = ( + options?: t.DeleteSharedLinkOptions, +): UseMutationResult => { + const queryClient = useQueryClient(); + const { onSuccess, ..._options } = options || {}; + return useMutation(({ shareId }) => dataService.deleteSharedLink(shareId), { + onSuccess: (_data, vars, context) => { + if (!vars.shareId) { + return; + } + + queryClient.setQueryData([QueryKeys.sharedMessages, vars.shareId], null); + queryClient.setQueryData([QueryKeys.sharedLinks], (data) => { + if (!data) { + return data; + } + return deleteSharedLink(data, vars.shareId); + }); + onSuccess?.(_data, vars, context); + }, + ...(_options || {}), + }); +}; + export const useDeleteConversationMutation = ( options?: t.DeleteConversationOptions, ): UseMutationResult< diff --git a/client/src/data-provider/queries.ts b/client/src/data-provider/queries.ts index 47646361074..50302ad7edc 100644 --- a/client/src/data-provider/queries.ts +++ b/client/src/data-provider/queries.ts @@ -20,6 +20,8 @@ import type { AssistantDocument, TEndpointsConfig, TCheckUserKeyResponse, + SharedLinkListParams, + SharedLinksResponse, } from 'librechat-data-provider'; import { findPageForConversation, addFileToCache } from '~/utils'; @@ -92,10 +94,10 @@ export const useGetConvoIdQuery = ( return defaultQuery(); } - const { pageIndex, convIndex } = findPageForConversation(convosQuery, { conversationId: id }); + const { pageIndex, index } = findPageForConversation(convosQuery, { conversationId: id }); - if (pageIndex > -1 && convIndex > -1) { - return convosQuery.pages[pageIndex].conversations[convIndex]; + if (pageIndex > -1 && index > -1) { + return convosQuery.pages[pageIndex].conversations[index]; } return defaultQuery(); @@ -158,6 +160,33 @@ export const useConversationsInfiniteQuery = ( ); }; +export const useSharedLinksInfiniteQuery = ( + params?: SharedLinkListParams, + config?: UseInfiniteQueryOptions, +) => { + return useInfiniteQuery( + [QueryKeys.sharedLinks], + ({ pageParam = '' }) => + dataService.listSharedLinks({ + ...params, + pageNumber: pageParam?.toString(), + isPublic: params?.isPublic || true, + }), + { + getNextPageParam: (lastPage) => { + const currentPageNumber = Number(lastPage.pageNumber); + const totalPages = Number(lastPage.pages); // Convert totalPages to a number + // If the current page number is less than total pages, return the next page number + return currentPageNumber < totalPages ? currentPageNumber + 1 : undefined; + }, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + ...config, + }, + ); +}; + /** * ASSISTANTS */ diff --git a/client/src/hooks/Nav/useNavScrolling.ts b/client/src/hooks/Nav/useNavScrolling.ts index ba45d3eabf4..635a7bc6a0c 100644 --- a/client/src/hooks/Nav/useNavScrolling.ts +++ b/client/src/hooks/Nav/useNavScrolling.ts @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useRef } from 'react'; import type { FetchNextPageOptions, InfiniteQueryObserverResult } from '@tanstack/react-query'; import type { ConversationListResponse } from 'librechat-data-provider'; -export default function useNavScrolling({ +export default function useNavScrolling({ hasNextPage, isFetchingNextPage, setShowLoading, @@ -14,7 +14,7 @@ export default function useNavScrolling({ setShowLoading: React.Dispatch>; fetchNextPage: ( options?: FetchNextPageOptions | undefined, - ) => Promise>; + ) => Promise>; }) { const scrollPositionRef = useRef(null); const containerRef = useRef(null); diff --git a/client/src/localization/languages/Ar.ts b/client/src/localization/languages/Ar.ts index ef8219c805f..942d7d24494 100644 --- a/client/src/localization/languages/Ar.ts +++ b/client/src/localization/languages/Ar.ts @@ -51,6 +51,17 @@ export default { com_ui_import_conversation_error: 'حدث خطأ أثناء استيراد محادثاتك', com_ui_confirm_action: 'تأكيد الإجراء', com_ui_chats: 'الدردشات', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: 'حذف', com_ui_delete_conversation: 'حذف الدردشة؟', com_ui_delete_conversation_confirm: 'سيتم حذف هذا', @@ -254,6 +265,12 @@ export default { com_nav_export_recursive_or_sequential: 'التراجع أو التسلسل؟', com_nav_export_recursive: 'تكراري', com_nav_export_conversation: 'تصدير المحادثة', + com_nav_export: 'تصدير', + com_nav_shared_links: 'روابط مشتركة', + com_nav_shared_links_manage: 'الإدارة', + com_nav_shared_links_empty: 'ليس لديك أي روابط مشتركة.', + com_nav_shared_links_name: 'الاسم', + com_nav_shared_links_date_shared: 'تاريخ المشترك', com_nav_theme: 'المظهر', com_nav_theme_system: 'النظام', com_nav_theme_dark: 'داكن', @@ -1456,6 +1473,10 @@ export const comparisons = { english: 'Export conversation', translated: 'تصدير المحادثة', }, + com_nav_export: { + english: 'Export', + translated: 'تصدير', + }, com_nav_theme: { english: 'Theme', translated: 'المظهر', diff --git a/client/src/localization/languages/Br.ts b/client/src/localization/languages/Br.ts index 81c0b037269..765541a3099 100644 --- a/client/src/localization/languages/Br.ts +++ b/client/src/localization/languages/Br.ts @@ -135,6 +135,17 @@ export default { com_ui_assistants_output: 'Saída dos Assistentes', com_ui_delete: 'Excluir', com_ui_create: 'Criar', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete_conversation: 'Excluir conversa?', com_ui_delete_conversation_confirm: 'Isso excluirá', com_ui_delete_assistant_confirm: @@ -216,9 +227,9 @@ export default { com_endpoint_bing_to_enable_sydney: 'Para habilitar Sydney', com_endpoint_bing_jailbreak: 'Jailbreak', com_endpoint_bing_context_placeholder: - 'O Bing pode usar até 7k tokens para \'contexto\', que ele pode referenciar para a conversa. O limite específico não é conhecido, mas pode causar erros ao exceder 7k tokens', + "O Bing pode usar até 7k tokens para 'contexto', que ele pode referenciar para a conversa. O limite específico não é conhecido, mas pode causar erros ao exceder 7k tokens", com_endpoint_bing_system_message_placeholder: - 'AVISO: O uso indevido deste recurso pode fazer com que você seja BANIDO de usar o Bing! Clique em \'Mensagem do Sistem\' para obter instruções completas e a mensagem padrão, caso omitida, que é a predefinição \'Sydney\', considerada segura.', + "AVISO: O uso indevido deste recurso pode fazer com que você seja BANIDO de usar o Bing! Clique em 'Mensagem do Sistem' para obter instruções completas e a mensagem padrão, caso omitida, que é a predefinição 'Sydney', considerada segura.", com_endpoint_system_message: 'Mensagem do Sistema', com_endpoint_message: 'Conversar com', com_endpoint_message_not_appendable: 'Edite sua mensagem ou Regenere.', @@ -373,7 +384,7 @@ export default { com_endpoint_config_key_edge_instructions: 'instruções', com_endpoint_config_key_edge_full_key_string: 'para fornecer as strings completas de cookies.', com_endpoint_config_key_chatgpt: - 'Para obter seu Token de Acesso para o ChatGPT \'Versão Gratuita\', faça login em', + "Para obter seu Token de Acesso para o ChatGPT 'Versão Gratuita', faça login em", com_endpoint_config_key_chatgpt_then_visit: 'então visite', com_endpoint_config_key_chatgpt_copy_token: 'Copiar token de acesso.', com_endpoint_config_key_google_need_to: 'Você precisa', @@ -381,7 +392,7 @@ export default { com_endpoint_config_key_google_vertex_api: 'API no Google Cloud, então', com_endpoint_config_key_google_service_account: 'Criar uma Conta de Serviço', com_endpoint_config_key_google_vertex_api_role: - 'Certifique-se de clicar em \'Criar e Continuar\' para dar pelo menos a função \'Usuário do Vertex AI\'. Por último, crie uma chave JSON para importar aqui.', + "Certifique-se de clicar em 'Criar e Continuar' para dar pelo menos a função 'Usuário do Vertex AI'. Por último, crie uma chave JSON para importar aqui.", com_nav_welcome_assistant: 'Por favor, Selecione um Assistente', com_nav_welcome_message: 'Como posso ajudar você hoje?', com_nav_auto_scroll: 'Auto-rolagem para o mais recente ao abrir', @@ -417,6 +428,12 @@ export default { com_nav_export_recursive_or_sequential: 'Recursivo ou sequencial?', com_nav_export_recursive: 'Recursivo', com_nav_export_conversation: 'Exportar conversa', + com_nav_export: 'Exportar', + com_nav_shared_links: 'Links Compartilhados', + com_nav_shared_links_manage: 'Gerenciar', + com_nav_shared_links_empty: 'Você não tem nenhum link compartilhado.', + com_nav_shared_links_name: 'Nome', + com_nav_shared_links_date_shared: 'Data compartilhada', com_nav_my_files: 'Meus arquivos', com_nav_theme: 'Tema', com_nav_theme_system: 'Sistema', @@ -1951,6 +1968,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Exportar conversa', }, + com_nav_export: { + english: 'Export', + translated: 'Exportar', + }, com_nav_my_files: { english: 'My Files', translated: 'Meus arquivos', diff --git a/client/src/localization/languages/De.ts b/client/src/localization/languages/De.ts index ac6ecec7bcc..29d5f0ebde7 100644 --- a/client/src/localization/languages/De.ts +++ b/client/src/localization/languages/De.ts @@ -148,6 +148,17 @@ export default { com_ui_assistants_output: 'Assistenten Ausgabe', com_ui_delete: 'Löschen', com_ui_create: 'Erstellen', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete_conversation: 'Chat löschen?', com_ui_delete_conversation_confirm: 'Damit wird gelöscht', com_ui_delete_assistant_confirm: @@ -431,6 +442,12 @@ export default { com_nav_export_recursive_or_sequential: 'Rekursiv oder sequentiell?', com_nav_export_recursive: 'Rekursiv', com_nav_export_conversation: 'Konversation exportieren', + com_nav_export: 'Exportieren', + com_nav_shared_links: 'Gemeinsame Links', + com_nav_shared_links_manage: 'Verwalten', + com_nav_shared_links_empty: 'Sie haben keine gemeinsam genutzten Links.', + com_nav_shared_links_name: 'Name', + com_nav_shared_links_date_shared: 'Datum geteilt', com_nav_my_files: 'Meine Dateien', com_nav_theme: 'Farbschema', com_nav_theme_system: 'System', @@ -2097,6 +2114,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Konversation exportieren', }, + com_nav_export: { + english: 'Export', + translated: 'Exportieren', + }, com_nav_my_files: { english: 'My Files', translated: 'Meine Dateien', diff --git a/client/src/localization/languages/Eng.ts b/client/src/localization/languages/Eng.ts index 2007224fc17..67bb8b656cc 100644 --- a/client/src/localization/languages/Eng.ts +++ b/client/src/localization/languages/Eng.ts @@ -199,6 +199,17 @@ export default { com_ui_assistants_output: 'Assistants Output', com_ui_delete: 'Delete', com_ui_create: 'Create', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete_conversation: 'Delete chat?', com_ui_delete_conversation_confirm: 'This will delete', com_ui_delete_assistant_confirm: @@ -480,6 +491,13 @@ export default { com_nav_export_recursive_or_sequential: 'Recursive or sequential?', com_nav_export_recursive: 'Recursive', com_nav_export_conversation: 'Export conversation', + com_nav_export: 'Export', + com_nav_shared_links: 'Shared links', + com_nav_shared_links_manage: 'Manage', + com_nav_shared_links_empty: 'You have no shared links.', + com_nav_shared_links_name: 'Name', + com_nav_shared_links_date_shared: 'Date shared', + com_nav_source_chat: 'View source chat', com_nav_my_files: 'My Files', com_nav_theme: 'Theme', com_nav_theme_system: 'System', diff --git a/client/src/localization/languages/Es.ts b/client/src/localization/languages/Es.ts index 66669791e5a..8644b4838d1 100644 --- a/client/src/localization/languages/Es.ts +++ b/client/src/localization/languages/Es.ts @@ -137,6 +137,17 @@ export default { com_ui_assistants_output: 'Salida de Asistentes', com_ui_delete: 'Eliminar', com_ui_create: 'Crear', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete_conversation: '¿Eliminar Chat?', com_ui_delete_conversation_confirm: 'Esto eliminará', com_ui_delete_assistant_confirm: @@ -423,6 +434,12 @@ export default { com_nav_export_recursive_or_sequential: '¿Recursivo o secuencial?', com_nav_export_recursive: 'Recursivo', com_nav_export_conversation: 'Exportar conversación', + com_nav_export: 'Exportar', + com_nav_shared_links: 'Links Compartidos', + com_nav_shared_links_manage: 'Gerenciar', + com_nav_shared_links_empty: 'Você não tem nenhum link compartilhado.', + com_nav_shared_links_name: 'Nome', + com_nav_shared_links_date_shared: 'Data compartilhada', com_nav_my_files: 'Mis archivos', com_nav_theme: 'Tema', com_nav_theme_system: 'Sistema', @@ -2069,6 +2086,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Exportar conversación', }, + com_nav_export: { + english: 'Export', + translated: 'Exportar', + }, com_nav_my_files: { english: 'My Files', translated: 'Mis archivos', diff --git a/client/src/localization/languages/Fr.ts b/client/src/localization/languages/Fr.ts index ea7ea122421..0c33e9013e8 100644 --- a/client/src/localization/languages/Fr.ts +++ b/client/src/localization/languages/Fr.ts @@ -65,6 +65,17 @@ export default { 'Une erreur s’est produite lors de l’importation de vos conversations', com_ui_confirm_action: 'Confirmer l\'action', com_ui_chats: 'discussions', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: 'Supprimer', com_ui_delete_conversation: 'Supprimer la discussions?', com_ui_delete_conversation_confirm: 'Cela supprimera', @@ -318,6 +329,12 @@ export default { com_nav_export_recursive_or_sequential: 'Récursif ou séquentiel ?', com_nav_export_recursive: 'Récursif', com_nav_export_conversation: 'Exporter la conversation', + com_nav_export: 'Exporter', + com_nav_shared_links: 'Liens partagés', + com_nav_shared_links_manage: 'Gerenciar', + com_nav_shared_links_empty: 'Você não tem nenhum link compartilhado.', + com_nav_shared_links_name: 'Nome', + com_nav_shared_links_date_shared: 'Data compartilhada', com_nav_theme: 'Thème', com_nav_theme_system: 'Système', com_nav_theme_dark: 'Sombre', @@ -1670,6 +1687,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Exporter la conversation', }, + com_nav_export: { + english: 'Export', + translated: 'Exporter', + }, com_nav_theme: { english: 'Theme', translated: 'Thème', diff --git a/client/src/localization/languages/He.ts b/client/src/localization/languages/He.ts index ca887e2042d..ce24dd22ecb 100644 --- a/client/src/localization/languages/He.ts +++ b/client/src/localization/languages/He.ts @@ -91,6 +91,17 @@ export default { com_ui_assistant: 'סייען', com_ui_delete: 'מחק', com_ui_create: 'צור', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete_conversation: 'למחוק את השיחה (צאט)?', com_ui_delete_conversation_confirm: 'זה ימחק', com_ui_delete_assistant_confirm: @@ -162,9 +173,9 @@ export default { com_endpoint_bing_to_enable_sydney: 'כדי לאפשר את סידני', com_endpoint_bing_jailbreak: 'Jailbreak', com_endpoint_bing_context_placeholder: - 'בינג יכול להשתמש בעד 7,000 אסימונים עבור \'הקשר\', שאליהם הוא יכול להתייחס לשיחה. המגבלה הספציפית אינה ידועה אך עשויה להיתקל בשגיאות העולות על 7,000 אסימונים', + "בינג יכול להשתמש בעד 7,000 אסימונים עבור 'הקשר', שאליהם הוא יכול להתייחס לשיחה. המגבלה הספציפית אינה ידועה אך עשויה להיתקל בשגיאות העולות על 7,000 אסימונים", com_endpoint_bing_system_message_placeholder: - 'אזהרה: שימוש לרעה בתכונה זו יכול לגרום לך לאסור להשתמש ב-Bing! לחץ על \'הודעת מערכת\' לקבלת הוראות מלאות והודעת ברירת המחדל אם הושמטה, שהיא הקביעה המוגדרת מראש של \'Sydney\' שנחשבת בטוחה.', + "אזהרה: שימוש לרעה בתכונה זו יכול לגרום לך לאסור להשתמש ב-Bing! לחץ על 'הודעת מערכת' לקבלת הוראות מלאות והודעת ברירת המחדל אם הושמטה, שהיא הקביעה המוגדרת מראש של 'Sydney' שנחשבת בטוחה.", com_endpoint_system_message: 'הודעת מערכת', com_endpoint_message: 'הודעה', com_endpoint_message_not_appendable: 'ערוך את ההודעה שלך או צור מחדש.', @@ -218,7 +229,7 @@ export default { com_endpoint_openai_prompt_prefix_placeholder: 'הגדר הוראות מותאמות אישית לכלול בהודעת המערכת. ברירת מחדל: אין', com_endpoint_anthropic_temp: - 'נע בין 0 ל-1. השתמש בטמפ\' הקרובה יותר ל-0 עבור בחירה אנליטית / מרובה, וקרוב יותר ל-1 עבור משימות יצירתיות ויצירתיות. אנו ממליצים לשנות את זה או את Top P אבל לא את שניהם.', + "נע בין 0 ל-1. השתמש בטמפ' הקרובה יותר ל-0 עבור בחירה אנליטית / מרובה, וקרוב יותר ל-1 עבור משימות יצירתיות ויצירתיות. אנו ממליצים לשנות את זה או את Top P אבל לא את שניהם.", com_endpoint_anthropic_topp: 'Top-p משנה את האופן שבו המודל בוחר אסימונים לפלט. אסימונים נבחרים מבין רוב K (ראה פרמטר topK) הסביר לפחות עד שסכום ההסתברויות שלהם שווה לערך העליון-p.', com_endpoint_anthropic_topk: @@ -305,7 +316,7 @@ export default { 'השתמש בכלי מפתחים או בתוסף בזמן שאתה מחובר לאתר כדי להעתיק את התוכן של קובץ ה-cookie _U. אם זה נכשל, עקוב אחר אלה', com_endpoint_config_key_edge_instructions: 'הוראות', com_endpoint_config_key_edge_full_key_string: 'כדי לספק את מחרוזות העוגיות המלאות.', - com_endpoint_config_key_chatgpt: 'כדי לקבל את אסימון הגישה שלך ל-ChatGPT \'גרסה חינמית\', היכנס אל', + com_endpoint_config_key_chatgpt: "כדי לקבל את אסימון הגישה שלך ל-ChatGPT 'גרסה חינמית', היכנס אל", com_endpoint_config_key_chatgpt_then_visit: 'ואז בקר', com_endpoint_config_key_chatgpt_copy_token: 'העתק אסימון גישה.', com_endpoint_config_key_google_need_to: 'אתה צריך', @@ -313,7 +324,7 @@ export default { com_endpoint_config_key_google_vertex_api: 'API ב-Google Cloud, אז', com_endpoint_config_key_google_service_account: 'צור חשבון שירות', com_endpoint_config_key_google_vertex_api_role: - 'הקפד ללחוץ על \'צור והמשך\' כדי לתת לפחות את התפקיד \'Vertex AI User\'. לבסוף, צור מפתח JSON לייבא לכאן.', + "הקפד ללחוץ על 'צור והמשך' כדי לתת לפחות את התפקיד 'Vertex AI User'. לבסוף, צור מפתח JSON לייבא לכאן.", com_nav_welcome_message: 'איך אני יכול לעזור לך היום?', com_nav_auto_scroll: 'Auto-s גלול אל הכי חדש בפתיחה', com_nav_hide_panel: 'הסתר לוח הצד הימני ביותר', @@ -345,6 +356,12 @@ export default { com_nav_export_recursive_or_sequential: 'רקורסיבי או רציף?', com_nav_export_recursive: 'רקורסיבי', com_nav_export_conversation: 'ייצא שיחה', + com_nav_export: 'ייצא', + com_nav_shared_links: 'קישורים משותפים', + com_nav_shared_links_manage: 'ניהול', + com_nav_shared_links_empty: 'אין לך קישורים משותפים.', + com_nav_shared_links_name: 'שם', + com_nav_shared_links_date_shared: 'תאריך שיתוף', com_nav_theme: 'נושא', com_nav_theme_system: 'מערכת', com_nav_theme_dark: 'כהה', @@ -1659,6 +1676,10 @@ export const comparisons = { english: 'Export conversation', translated: 'ייצא שיחה', }, + com_nav_export: { + english: 'Export', + translated: 'ייצא', + }, com_nav_theme: { english: 'Theme', translated: 'נושא', diff --git a/client/src/localization/languages/Id.ts b/client/src/localization/languages/Id.ts index 16927392ae0..d047d9d8cf3 100644 --- a/client/src/localization/languages/Id.ts +++ b/client/src/localization/languages/Id.ts @@ -61,6 +61,17 @@ export default { com_ui_import_conversation_error: 'Terjadi kesalahan saat mengimpor percakapan Anda', com_ui_confirm_action: 'Konfirmasi Aksi', com_ui_chats: 'chat', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: 'Hapus', com_ui_delete_conversation: 'Hapus chat?', com_ui_delete_conversation_confirm: 'Ini akan menghapus', @@ -134,9 +145,9 @@ export default { com_endpoint_bing_to_enable_sydney: 'Untuk mengaktifkan Sydney', com_endpoint_bing_jailbreak: 'Jailbreak', com_endpoint_bing_context_placeholder: - 'Bing dapat menggunakan hingga 7k token untuk \'konteks\', yang dapat dirujuk untuk percakapan. Batas spesifik tidak diketahui tetapi mungkin menemui kesalahan melebihi 7k token', + "Bing dapat menggunakan hingga 7k token untuk 'konteks', yang dapat dirujuk untuk percakapan. Batas spesifik tidak diketahui tetapi mungkin menemui kesalahan melebihi 7k token", com_endpoint_bing_system_message_placeholder: - 'PERINGATAN: Penyalahgunaan fitur ini dapat membuat Anda DILARANG menggunakan Bing! Klik pada \'Pesan Sistem\' untuk instruksi lengkap dan pesan default jika diabaikan, yang merupakan preset \'Sydney\' yang dianggap aman.', + "PERINGATAN: Penyalahgunaan fitur ini dapat membuat Anda DILARANG menggunakan Bing! Klik pada 'Pesan Sistem' untuk instruksi lengkap dan pesan default jika diabaikan, yang merupakan preset 'Sydney' yang dianggap aman.", com_endpoint_system_message: 'Pesan Sistem', com_endpoint_message: 'Pesan', com_endpoint_message_not_appendable: 'Edit pesan Anda atau Regenerasi.', @@ -271,7 +282,7 @@ export default { com_endpoint_config_key_edge_instructions: 'instruksi', com_endpoint_config_key_edge_full_key_string: 'untuk memberikan string cookie lengkap.', com_endpoint_config_key_chatgpt: - 'Untuk mendapatkan token akses Anda Untuk ChatGPT \'Versi Gratis\', masuk ke', + "Untuk mendapatkan token akses Anda Untuk ChatGPT 'Versi Gratis', masuk ke", com_endpoint_config_key_chatgpt_then_visit: 'kemudian kunjungi', com_endpoint_config_key_chatgpt_copy_token: 'Salin token akses.', com_endpoint_config_key_google_need_to: 'Anda perlu', @@ -279,7 +290,7 @@ export default { com_endpoint_config_key_google_vertex_api: 'API di Google Cloud, kemudian', com_endpoint_config_key_google_service_account: 'Buat Akun Layanan', com_endpoint_config_key_google_vertex_api_role: - 'Pastikan untuk mengklik \'Buat dan Lanjutkan\' untuk memberikan setidaknya peran \'Pengguna Vertex AI\'. Terakhir, buat kunci JSON untuk diimpor di sini.', + "Pastikan untuk mengklik 'Buat dan Lanjutkan' untuk memberikan setidaknya peran 'Pengguna Vertex AI'. Terakhir, buat kunci JSON untuk diimpor di sini.", com_nav_welcome_message: 'Bagaimana saya bisa membantu Anda hari ini?', com_nav_auto_scroll: 'Otomatis gulir ke Baru saat Buka', com_nav_modular_chat: 'Aktifkan penggantian Endpoint di tengah percakapan', @@ -304,6 +315,12 @@ export default { com_nav_export_recursive_or_sequential: 'Rekursif atau berurutan?', com_nav_export_recursive: 'Rekursif', com_nav_export_conversation: 'Ekspor percakapan', + com_nav_export: 'Ekspor', + com_nav_shared_links: 'Link berbagi', + com_nav_shared_links_manage: 'Pengeluaran', + com_nav_shared_links_empty: 'Anda tidak memiliki link berbagi.', + com_nav_shared_links_name: 'Nama', + com_nav_shared_links_date_shared: 'Tanggal berbagi', com_nav_theme: 'Tema', com_nav_theme_system: 'Sistem', com_nav_theme_dark: 'Gelap', @@ -1457,6 +1474,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Ekspor percakapan', }, + com_nav_export: { + english: 'Export', + translated: 'Ekspor', + }, com_nav_theme: { english: 'Theme', translated: 'Tema', diff --git a/client/src/localization/languages/It.ts b/client/src/localization/languages/It.ts index 83daab8cc97..b00c303c136 100644 --- a/client/src/localization/languages/It.ts +++ b/client/src/localization/languages/It.ts @@ -188,6 +188,17 @@ export default { com_ui_assistants_output: 'Output Assistenti', com_ui_delete: 'Elimina', com_ui_create: 'Crea', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete_conversation: 'Eliminare la chat?', com_ui_delete_conversation_confirm: 'Questo eliminerà', com_ui_rename: 'Rinominare', @@ -475,6 +486,12 @@ export default { com_nav_export_recursive_or_sequential: 'Ricorsivo o sequenziale?', com_nav_export_recursive: 'Ricorsivo', com_nav_export_conversation: 'Esporta conversazione', + com_nav_export: 'Esporta', + com_nav_shared_links: 'Link condivisi', + com_nav_shared_links_manage: 'Gestisci', + com_nav_shared_links_empty: 'Non hai link condivisi.', + com_nav_shared_links_name: 'Nome', + com_nav_shared_links_date_shared: 'Data condivisione', com_nav_my_files: 'I miei file', com_nav_theme: 'Tema', com_nav_theme_system: 'Sistema', @@ -2234,6 +2251,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Esporta conversazione', }, + com_nav_export: { + english: 'Export', + translated: 'Esporta', + }, com_nav_my_files: { english: 'My Files', translated: 'I miei file', diff --git a/client/src/localization/languages/Jp.ts b/client/src/localization/languages/Jp.ts index 5a4227f649b..f8b11024d23 100644 --- a/client/src/localization/languages/Jp.ts +++ b/client/src/localization/languages/Jp.ts @@ -147,6 +147,18 @@ export default { com_ui_assistants_output: 'Assistantsの出力', com_ui_delete: '削除', com_ui_create: '作成', + com_ui_share: '共有', + com_ui_share_link_to_chat: 'チャットへの共有リンク', + com_ui_share_error: 'チャットの共有リンクの共有中にエラーが発生しました', + com_ui_share_create_message: + 'あなたの名前と共有リンクを作成した後のメッセージは、共有されません。', + com_ui_share_created_message: + 'チャットへの公開された共有リンクが作成されました。設定から以前共有したチャットを管理できます。', + com_ui_share_update_message: + 'あなたの名前、カスタム指示、共有リンクを作成した後のメッセージは、共有されません。', + com_ui_share_updated_message: + 'チャットへの公開された共有リンクが更新されました。設定から以前共有したチャットを管理できます。', + com_ui_shared_link_not_found: '共有リンクが見つかりません', com_ui_delete_conversation: 'チャットを削除しますか?', com_ui_delete_conversation_confirm: 'このチャットは削除されます。', com_ui_delete_assistant_confirm: 'このアシスタントを削除しますか? この操作は元に戻せません。', @@ -422,6 +434,12 @@ export default { com_nav_export_recursive_or_sequential: '再帰的? or 順次的?', com_nav_export_recursive: '再帰的', com_nav_export_conversation: '会話をエクスポートする', + com_nav_export: 'エクスポート', + com_nav_shared_links: '共有リンク', + com_nav_shared_links_manage: '管理', + com_nav_shared_links_empty: '共有リンクはありません。', + com_nav_shared_links_name: 'タイトル', + com_nav_shared_links_date_shared: '共有日', com_nav_my_files: 'My Files', com_nav_theme: 'テーマ', com_nav_theme_system: 'システム', @@ -2087,6 +2105,10 @@ export const comparisons = { english: 'Export conversation', translated: '会話をエクスポートする', }, + com_nav_export: { + english: 'Export', + translated: 'エクスポート', + }, com_nav_my_files: { english: 'My Files', translated: 'My Files', diff --git a/client/src/localization/languages/Ko.ts b/client/src/localization/languages/Ko.ts index d84bde4cee2..4dbfd3fa30b 100644 --- a/client/src/localization/languages/Ko.ts +++ b/client/src/localization/languages/Ko.ts @@ -50,6 +50,17 @@ export default { com_ui_import_conversation_error: '대화를 가져오는 동안 오류가 발생했습니다', com_ui_confirm_action: '작업 확인', com_ui_chats: '채팅', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: '삭제', com_ui_delete_conversation: '채팅을 삭제하시겠습니까?', com_ui_delete_conversation_confirm: '이 채팅이 삭제됩니다', @@ -234,6 +245,12 @@ export default { com_nav_export_recursive_or_sequential: '재귀적 또는 순차적?', com_nav_export_recursive: '재귀적', com_nav_export_conversation: '대화 내보내기', + com_nav_export: '내보내기', + com_nav_shared_links: '공유 링크', + com_nav_shared_links_manage: '관리', + com_nav_shared_links_empty: '공유 링크가 없습니다.', + com_nav_shared_links_name: '이름', + com_nav_shared_links_date_shared: '공유 날짜', com_nav_theme: '테마', com_nav_theme_system: '시스템', com_nav_theme_dark: '다크', @@ -1397,6 +1414,10 @@ export const comparisons = { english: 'Export conversation', translated: '대화 내보내기', }, + com_nav_export: { + english: 'Export', + translated: '내보내기', + }, com_nav_theme: { english: 'Theme', translated: '테마', diff --git a/client/src/localization/languages/Nl.ts b/client/src/localization/languages/Nl.ts index 647830dacbf..57cf69e19b3 100644 --- a/client/src/localization/languages/Nl.ts +++ b/client/src/localization/languages/Nl.ts @@ -54,6 +54,17 @@ export default { 'Er is een fout opgetreden bij het importeren van je gesprekken', com_ui_confirm_action: 'Bevestig actie', com_ui_chats: 'chats', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: 'Verwijderen', com_ui_delete_conversation: 'Chat verwijderen?', com_ui_delete_conversation_confirm: 'Hiermee wordt', @@ -125,9 +136,9 @@ export default { com_endpoint_bing_to_enable_sydney: 'Om Sydney in te schakelen', com_endpoint_bing_jailbreak: 'Jailbreak', com_endpoint_bing_context_placeholder: - 'Bing kan maximaal 7k tokens voor \'context\' gebruiken, waarnaar het kan verwijzen voor het gesprek. De specifieke limiet is niet bekend, maar kan fouten opleveren bij meer dan 7k tokens', + "Bing kan maximaal 7k tokens voor 'context' gebruiken, waarnaar het kan verwijzen voor het gesprek. De specifieke limiet is niet bekend, maar kan fouten opleveren bij meer dan 7k tokens", com_endpoint_bing_system_message_placeholder: - 'WAARSCHUWING: Misbruik van deze functie kan ertoe leiden dat je VERBANNEN wordt van het gebruik van Bing! Klik op \'Systeembericht\' voor volledige instructies en het standaardbericht indien weggelaten, wat de \'Sydney\'-voorinstelling is die veilig wordt geacht.', + "WAARSCHUWING: Misbruik van deze functie kan ertoe leiden dat je VERBANNEN wordt van het gebruik van Bing! Klik op 'Systeembericht' voor volledige instructies en het standaardbericht indien weggelaten, wat de 'Sydney'-voorinstelling is die veilig wordt geacht.", com_endpoint_system_message: 'Systeembericht', com_endpoint_default_blank: 'standaard: leeg', com_endpoint_default_false: 'standaard: onwaar', @@ -143,7 +154,7 @@ export default { com_endpoint_google_topp: 'Top-p verandert hoe het model tokens selecteert voor uitvoer. Tokens worden geselecteerd van meest K (zie topK-parameter) waarschijnlijk tot minst waarschijnlijk totdat de som van hun kansen gelijk is aan de top-p-waarde.', com_endpoint_google_topk: - 'Top-k verandert hoe het model tokens selecteert voor uitvoer. Een top-k van 1 betekent dat het geselecteerde token het meest waarschijnlijk is van alle tokens in de vocabulaire van het model (ook wel \'greedy decoding\' genoemd), terwijl een top-k van 3 betekent dat het volgende token wordt geselecteerd uit de 3 meest waarschijnlijke tokens (met behulp van temperatuur).', + "Top-k verandert hoe het model tokens selecteert voor uitvoer. Een top-k van 1 betekent dat het geselecteerde token het meest waarschijnlijk is van alle tokens in de vocabulaire van het model (ook wel 'greedy decoding' genoemd), terwijl een top-k van 3 betekent dat het volgende token wordt geselecteerd uit de 3 meest waarschijnlijke tokens (met behulp van temperatuur).", com_endpoint_google_maxoutputtokens: ' Maximum aantal tokens dat kan worden gegenereerd in de reactie. Geef een lagere waarde op voor kortere reacties en een hogere waarde voor langere reacties.', com_endpoint_google_custom_name_placeholder: 'Stel een aangepaste naam in voor Google', @@ -174,7 +185,7 @@ export default { com_endpoint_anthropic_topp: 'Top-p verandert hoe het model tokens selecteert voor uitvoer. Tokens worden geselecteerd van meest K (zie topK-parameter) waarschijnlijk tot minst waarschijnlijk totdat de som van hun kansen gelijk is aan de top-p-waarde.', com_endpoint_anthropic_topk: - 'Top-k verandert hoe het model tokens selecteert voor uitvoer. Een top-k van 1 betekent dat het geselecteerde token het meest waarschijnlijk is van alle tokens in de vocabulaire van het model (ook wel \'greedy decoding\' genoemd), terwijl een top-k van 3 betekent dat het volgende token wordt geselecteerd uit de 3 meest waarschijnlijke tokens (met behulp van temperatuur).', + "Top-k verandert hoe het model tokens selecteert voor uitvoer. Een top-k van 1 betekent dat het geselecteerde token het meest waarschijnlijk is van alle tokens in de vocabulaire van het model (ook wel 'greedy decoding' genoemd), terwijl een top-k van 3 betekent dat het volgende token wordt geselecteerd uit de 3 meest waarschijnlijke tokens (met behulp van temperatuur).", com_endpoint_anthropic_maxoutputtokens: 'Maximum aantal tokens dat kan worden gegenereerd in de reactie. Geef een lagere waarde op voor kortere reacties en een hogere waarde voor langere reacties.', com_endpoint_anthropic_custom_name_placeholder: 'Stel een aangepaste naam in voor Anthropic', @@ -237,7 +248,7 @@ export default { com_endpoint_config_key_edge_full_key_string: 'om de volledige cookie-tekenreeksen te verstrekken.', com_endpoint_config_key_chatgpt: - 'Om uw toegangstoken voor ChatGPT \'Gratis versie\' te krijgen, logt u in op', + "Om uw toegangstoken voor ChatGPT 'Gratis versie' te krijgen, logt u in op", com_endpoint_config_key_chatgpt_then_visit: 'ga vervolgens naar', com_endpoint_config_key_chatgpt_copy_token: 'Kopieer toegangstoken.', com_endpoint_config_key_google_need_to: 'U moet', @@ -245,7 +256,7 @@ export default { com_endpoint_config_key_google_vertex_api: 'API op Google Cloud, dan', com_endpoint_config_key_google_service_account: 'Maak een serviceaccount', com_endpoint_config_key_google_vertex_api_role: - 'Zorg ervoor dat u op \'Maken en doorgaan\' klikt om ten minste de \'Vertex AI-gebruiker\'-rol te geven. Maak ten slotte een JSON-sleutel aan om hier te importeren.', + "Zorg ervoor dat u op 'Maken en doorgaan' klikt om ten minste de 'Vertex AI-gebruiker'-rol te geven. Maak ten slotte een JSON-sleutel aan om hier te importeren.", com_nav_auto_scroll: 'Automatisch scrollen naar Nieuwste bij openen', com_nav_plugin_store: 'Plugin-opslag', com_nav_plugin_search: 'Plugins zoeken', @@ -261,6 +272,12 @@ export default { com_nav_export_recursive_or_sequential: 'Recursief of sequentieel?', com_nav_export_recursive: 'Recursief', com_nav_export_conversation: 'Conversatie exporteren', + com_nav_export: 'Exporteren', + com_nav_shared_links: 'Gedeelde links', + com_nav_shared_links_manage: 'Beheren', + com_nav_shared_links_empty: 'U hebt geen gedeeld links.', + com_nav_shared_links_name: 'Naam', + com_nav_shared_links_date_shared: 'Datum gedeeld', com_nav_theme: 'Thema', com_nav_theme_system: 'Systeem', com_nav_theme_dark: 'Donker', @@ -1220,6 +1237,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Conversatie exporteren', }, + com_nav_export: { + english: 'Export', + translated: 'Exporteren', + }, com_nav_theme: { english: 'Theme', translated: 'Thema', diff --git a/client/src/localization/languages/Pl.ts b/client/src/localization/languages/Pl.ts index 944cab7a418..1ceaed5b6a4 100644 --- a/client/src/localization/languages/Pl.ts +++ b/client/src/localization/languages/Pl.ts @@ -30,6 +30,17 @@ export default { com_ui_entries: 'wpisów', com_ui_pay_per_call: 'Wszystkie rozmowy z AI w jednym miejscu. Płatność za połączenie, a nie za miesiąc', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_rename: 'Zmień nazwę', com_ui_archive: 'Archiwum', com_ui_archive_error: 'Nie udało się archiwizować rozmowy', @@ -92,9 +103,9 @@ export default { com_endpoint_bing_to_enable_sydney: 'Aby aktywować Sydney', com_endpoint_bing_jailbreak: 'Odblokuj', com_endpoint_bing_context_placeholder: - 'Bing może użyć do 7k tokenów dla \'kontekstu\', które mogą odnosić się do rozmowy. Dokładny limit nie jest znany, ale przekroczenie 7 tysięcy tokenów może prowadzić do błędów.', + "Bing może użyć do 7k tokenów dla 'kontekstu', które mogą odnosić się do rozmowy. Dokładny limit nie jest znany, ale przekroczenie 7 tysięcy tokenów może prowadzić do błędów.", com_endpoint_bing_system_message_placeholder: - 'OSTRZEŻENIE: Nadużywanie tej funkcji może skutkować ZAKAZEM korzystania z Bing! Kliknij na \'Wiadomość systemowa\' , aby uzyskać pełne instrukcje oraz domyślną wiadomość, jeśli zostanie pominięta, co jest predefiniowaną opcją \'Sydney\', uważaną za bezpieczną.', + "OSTRZEŻENIE: Nadużywanie tej funkcji może skutkować ZAKAZEM korzystania z Bing! Kliknij na 'Wiadomość systemowa' , aby uzyskać pełne instrukcje oraz domyślną wiadomość, jeśli zostanie pominięta, co jest predefiniowaną opcją 'Sydney', uważaną za bezpieczną.", com_endpoint_system_message: 'Wiadomość systemowa', com_endpoint_default_blank: 'domyślnie: puste', com_endpoint_default_false: 'domyślnie: fałsz', @@ -192,6 +203,11 @@ export default { com_nav_export_recursive_or_sequential: 'Rekurencyjny czy sekwencyjny?', com_nav_export_recursive: 'Rekurencyjny', com_nav_export_conversation: 'Eksportuj konwersację', + com_nav_shared_links: 'Linki udostępnione', + com_nav_shared_links_manage: 'Beheren', + com_nav_shared_links_empty: 'U hebt geen gedeeld links.', + com_nav_shared_links_name: 'Naam', + com_nav_shared_links_date_shared: 'Datum gedeeld', com_nav_theme: 'Motyw', com_nav_theme_system: 'Domyślny', com_nav_theme_dark: 'Ciemny', diff --git a/client/src/localization/languages/Ru.ts b/client/src/localization/languages/Ru.ts index 26d44264e4f..7891182ea42 100644 --- a/client/src/localization/languages/Ru.ts +++ b/client/src/localization/languages/Ru.ts @@ -66,6 +66,17 @@ export default { com_ui_preview: 'Предпросмотр', com_ui_upload: 'Загрузить', com_ui_connect: 'Подключить', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete_conversation: 'Удалить чат?', com_ui_delete_conversation_confirm: 'Будет удален следующий чат: ', com_ui_rename: 'Переименовать', @@ -302,6 +313,12 @@ export default { com_nav_export_recursive_or_sequential: 'Рекурсивно или последовательно?', com_nav_export_recursive: 'Рекурсивно', com_nav_export_conversation: 'Экспортировать разговор', + com_nav_export: 'Экспорт', + com_nav_shared_links: 'Связываемые ссылки', + com_nav_shared_links_manage: 'Управление', + com_nav_shared_links_empty: 'У вас нет связываемых ссылок.', + com_nav_shared_links_name: 'Naam', + com_nav_shared_links_date_shared: 'Datum gedeeld', com_nav_my_files: 'Мои файлы', com_nav_theme: 'Тема', com_nav_theme_system: 'Системная', @@ -1635,6 +1652,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Экспортировать разговор', }, + com_nav_export: { + english: 'Export', + translated: 'Экспорт', + }, com_nav_my_files: { english: 'My Files', translated: 'Мои файлы', diff --git a/client/src/localization/languages/Sv.ts b/client/src/localization/languages/Sv.ts index d5debae8244..4973d251c59 100644 --- a/client/src/localization/languages/Sv.ts +++ b/client/src/localization/languages/Sv.ts @@ -51,6 +51,17 @@ export default { com_ui_import_conversation_error: 'Det uppstod ett fel vid import av dina konversationer', com_ui_confirm_action: 'Bekräfta åtgärd', com_ui_chats: 'chattar', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: 'Radera', com_ui_delete_conversation: 'Radera chatt?', com_ui_delete_conversation_confirm: 'Detta kommer att radera', @@ -249,6 +260,12 @@ export default { com_nav_export_recursive_or_sequential: 'Rekursiv eller sekventiell?', com_nav_export_recursive: 'Rekursiv', com_nav_export_conversation: 'Exportera konversation', + com_nav_export: 'Exportera', + com_nav_shared_links: 'Delade länkar', + com_nav_shared_links_manage: 'Hantera', + com_nav_shared_links_empty: 'Du har inga delade länkar.', + com_nav_shared_links_name: 'Namn', + com_nav_shared_links_date_shared: 'Datum delad', com_nav_theme: 'Tema', com_nav_theme_system: 'System', com_nav_theme_dark: 'Mörkt', @@ -1185,6 +1202,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Exportera konversation', }, + com_nav_export: { + english: 'Export', + translated: 'Exportera', + }, com_nav_theme: { english: 'Theme', translated: 'Tema', diff --git a/client/src/localization/languages/Tr.ts b/client/src/localization/languages/Tr.ts index 959145dfc51..8b4bbc0dd09 100644 --- a/client/src/localization/languages/Tr.ts +++ b/client/src/localization/languages/Tr.ts @@ -7,7 +7,7 @@ export default { com_ui_example_quantum_computing: 'Kuantum bilgisayarını basit terimlerle açıkla', com_ui_example_10_year_old_b_day: '10 yaşındaki bir çocuğun doğum günü için yaratıcı fikirlerin var mı?', - com_ui_example_http_in_js: 'Javascript\'te HTTP isteği nasıl yapılır?', + com_ui_example_http_in_js: "Javascript'te HTTP isteği nasıl yapılır?", com_ui_capabilities: 'Yetenekler', com_ui_capability_remember: 'Kullanıcının önceki konuşmada söylediklerini hatırlar', com_ui_capability_correction: 'Kullanıcının düzeltme yapmasına izin verir', @@ -53,6 +53,17 @@ export default { com_ui_import_conversation_error: 'Sohbetlerinizi içe aktarırken bir hata oluştu', com_ui_confirm_action: 'İşlemi Onayla', com_ui_chats: 'sohbetler', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: 'Sil', com_ui_delete_conversation: 'Sohbet silinecek?', com_ui_delete_conversation_confirm: 'Bu silinecek', @@ -120,8 +131,8 @@ export default { com_auth_submit_registration: 'Kaydı Gönder', com_auth_welcome_back: 'Tekrar Hoş Geldiniz', com_endpoint_open_menu: 'Menüyü Aç', - com_endpoint_bing_enable_sydney: 'Sydney\'i Etkinleştir', - com_endpoint_bing_to_enable_sydney: 'Sydney\'i etkinleştirmek için', + com_endpoint_bing_enable_sydney: "Sydney'i Etkinleştir", + com_endpoint_bing_to_enable_sydney: "Sydney'i etkinleştirmek için", com_endpoint_bing_jailbreak: 'Jailbreak', com_endpoint_bing_context_placeholder: 'Bing, konuşma için başvurabileceği "bağlam" için 7k tokena kadar kullanabilir. Belirli bir sınır bilinmemekle birlikte, 7k tokeni aşan hatalara neden olabilir', @@ -142,7 +153,7 @@ export default { com_endpoint_google_temp: 'Daha yüksek değerler = daha rastgele, daha düşük değerler = daha odaklanmış ve belirleyici. Bunlardan birini değiştirmenizi öneririz, ancak her ikisini birden değil.', com_endpoint_google_topp: - 'Top-p, modelin çıkış için token seçme şeklini değiştirir. Token\'lar, en yüksek K (topK parametresini görmek için) olasılıktan en düşük olasılığa kadar seçilir, toplam olasılıkları top-p değerine eşit olana kadar.', + "Top-p, modelin çıkış için token seçme şeklini değiştirir. Token'lar, en yüksek K (topK parametresini görmek için) olasılıktan en düşük olasılığa kadar seçilir, toplam olasılıkları top-p değerine eşit olana kadar.", com_endpoint_google_topk: 'Top-k, modelin çıkış için token seçme şeklini değiştirir. 1 top-k, seçilen tokenın modelin kelime dağarcığındaki tüm tokenlar arasında en olası olduğu anlamına gelir (ayrıca aç gözlü kod çözme denir), 3 top-k ise bir sonraki tokenın 3 en olası token arasından seçildiği anlamına gelir (sıcaklık kullanılarak).', com_endpoint_google_maxoutputtokens: @@ -162,18 +173,18 @@ export default { com_endpoint_openai_max: 'Oluşturulacak maksimum token sayısı. Giriş tokenlarının toplam uzunluğu, modelin bağlam uzunluğu tarafından sınırlanır.', com_endpoint_openai_topp: - 'Sıcaklıkla örnekleme alternatifi olan bir başka seçenek, modelin top_p olasılık kütlesine sahip tokenların sonuçlarını düşünmesidir. Bu nedenle 0.1, yalnızca top 10% olasılık kütlesini oluşturan token\'ların düşünüldüğü anlamına gelir. Bunları veya sıcaklığı değiştirmenizi öneririz, ancak her ikisini birden değil.', + "Sıcaklıkla örnekleme alternatifi olan bir başka seçenek, modelin top_p olasılık kütlesine sahip tokenların sonuçlarını düşünmesidir. Bu nedenle 0.1, yalnızca top 10% olasılık kütlesini oluşturan token'ların düşünüldüğü anlamına gelir. Bunları veya sıcaklığı değiştirmenizi öneririz, ancak her ikisini birden değil.", com_endpoint_openai_freq: 'Metindeki mevcut frekanslarına dayanarak yeni tokenları cezalandırmak için -2.0 ile 2.0 arasında bir sayı. Pozitif değerler, modelin aynı satırı kelimesi kelimesine tekrar etme olasılığını azaltır.', com_endpoint_openai_pres: 'Metindeki varolup olmadıklarına dayanarak yeni tokenları cezalandırmak için -2.0 ile 2.0 arasında bir sayı. Pozitif değerler, modelin yeni konulardan bahsetme olasılığını artırır.', com_endpoint_openai_custom_name_placeholder: 'ChatGPT için özel bir ad belirleyin', com_endpoint_openai_prompt_prefix_placeholder: - 'Sistem Mesajı\'na dahil edilecek özel talimatları ayarlayın. Varsayılan: yok', + "Sistem Mesajı'na dahil edilecek özel talimatları ayarlayın. Varsayılan: yok", com_endpoint_anthropic_temp: '0 ile 1 arasında bir değer. Analitik / çoklu seçim için daha yakın bir sıcaklık kullanın ve yaratıcı ve üretken görevler için daha yakın 1 kullanın. Bunlardan birini değiştirmenizi öneririz, ancak her ikisini birden değil.', com_endpoint_anthropic_topp: - 'Top-p, modelin çıkış için token seçme şeklini değiştirir. Token\'lar, en yüksek K (topK parametresini görmek için) olasılıktan en düşük olasılığa kadar seçilir, toplam olasılıkları top-p değerine eşit olana kadar.', + "Top-p, modelin çıkış için token seçme şeklini değiştirir. Token'lar, en yüksek K (topK parametresini görmek için) olasılıktan en düşük olasılığa kadar seçilir, toplam olasılıkları top-p değerine eşit olana kadar.", com_endpoint_anthropic_topk: 'Top-k, modelin çıkış için token seçme şeklini değiştirir. 1 top-k, seçilen tokenın modelin kelime dağarcığındaki tüm tokenlar arasında en olası olduğu anlamına gelir (ayrıca aç gözlü kod çözme denir), 3 top-k ise bir sonraki tokenın 3 en olası token arasından seçildiği anlamına gelir (sıcaklık kullanılarak).', com_endpoint_anthropic_maxoutputtokens: @@ -186,7 +197,7 @@ export default { com_endpoint_disabled_with_tools: 'araçlarla devre dışı bırakıldı', com_endpoint_disabled_with_tools_placeholder: 'Araçlar Seçiliyken Devre Dışı Bırakıldı', com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: - 'Sistem Mesajı\'na dahil edilecek özel talimatları ayarlayın. Varsayılan: hiçbiri', + "Sistem Mesajı'na dahil edilecek özel talimatları ayarlayın. Varsayılan: hiçbiri", com_endpoint_import: 'İçe Aktar', com_endpoint_set_custom_name: 'Bu ön ayarı bulabilmeniz için özel bir ad belirleyin', com_endpoint_preset_delete_confirm: 'Bu ön ayarı silmek istediğinizden emin misiniz?', @@ -252,15 +263,15 @@ export default { com_endpoint_config_key_edge_instructions: 'talimatları', com_endpoint_config_key_edge_full_key_string: 'tam çerez dizilerini sağlamak için.', com_endpoint_config_key_chatgpt: - 'ChatGPT \'Free Version\' için Erişim belirtecinizi almak için giriş yapın', + "ChatGPT 'Free Version' için Erişim belirtecinizi almak için giriş yapın", com_endpoint_config_key_chatgpt_then_visit: 'ardından ziyaret edin', com_endpoint_config_key_chatgpt_copy_token: 'Erişim belirtecini kopyalayın.', com_endpoint_config_key_google_need_to: 'Şunlara ihtiyacınız var:', - com_endpoint_config_key_google_vertex_ai: 'Vertex AI\'yi Etkinleştir', - com_endpoint_config_key_google_vertex_api: 'Google Cloud\'da API\'yi Etkinleştirin, ardından', + com_endpoint_config_key_google_vertex_ai: "Vertex AI'yi Etkinleştir", + com_endpoint_config_key_google_vertex_api: "Google Cloud'da API'yi Etkinleştirin, ardından", com_endpoint_config_key_google_service_account: 'Bir Servis Hesabı Oluşturun', com_endpoint_config_key_google_vertex_api_role: - 'Lütfen \'Oluştur ve Devam Et\'e tıklayarak en az \'Vertex AI Kullanıcısı\' rolünü verdiğinizden emin olun. Son olarak, buraya içe aktarmak için bir JSON anahtarı oluşturun.', + "Lütfen 'Oluştur ve Devam Et'e tıklayarak en az 'Vertex AI Kullanıcısı' rolünü verdiğinizden emin olun. Son olarak, buraya içe aktarmak için bir JSON anahtarı oluşturun.", com_nav_welcome_message: 'Bugün size nasıl yardımcı olabilirim?', com_nav_auto_scroll: 'Açıkta En Yeniye Otomatik Kaydır', com_nav_plugin_store: 'Eklenti Mağazası', @@ -277,6 +288,12 @@ export default { com_nav_export_recursive_or_sequential: 'Yinelemeli mi yoksa sıralı mı?', com_nav_export_recursive: 'Yinelemeli', com_nav_export_conversation: 'Konuşmayı dışa aktar', + com_nav_export: 'Dışa Aktar', + com_nav_shared_links: 'Paylaşılan linkler', + com_nav_shared_links_manage: 'Ynetmek', + com_nav_shared_links_empty: 'Paylaşılan linkleriniz yok.', + com_nav_shared_links_name: 'İsim', + com_nav_shared_links_date_shared: 'Paylaşılan tarih', com_nav_theme: 'Tema', com_nav_theme_system: 'Sistem', com_nav_theme_dark: 'Koyu', @@ -1302,6 +1319,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Konuşmayı dışa aktar', }, + com_nav_export: { + english: 'Export', + translated: 'Dışa Aktar', + }, com_nav_theme: { english: 'Theme', translated: 'Tema', diff --git a/client/src/localization/languages/Vi.ts b/client/src/localization/languages/Vi.ts index 9229afc2e9c..d583089b160 100644 --- a/client/src/localization/languages/Vi.ts +++ b/client/src/localization/languages/Vi.ts @@ -53,6 +53,17 @@ export default { com_ui_import_conversation_error: 'Đã xảy ra lỗi khi nhập khẩu cuộc trò chuyện của bạn', com_ui_confirm_action: 'Xác nhận hành động', com_ui_chats: 'cuộc trò chuyện', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: 'Xóa', com_ui_delete_conversation: 'Xóa cuộc trò chuyện?', com_ui_delete_conversation_confirm: 'Điều này sẽ xóa', @@ -124,9 +135,9 @@ export default { com_endpoint_bing_to_enable_sydney: 'Để bật Sydney', com_endpoint_bing_jailbreak: 'Bẻ khóa', com_endpoint_bing_context_placeholder: - 'Bing có thể sử dụng tối đa 7k mã thông báo cho \'ngữ cảnh\', mà nó có thể tham khảo trong cuộc trò chuyện. Giới hạn cụ thể không được biết nhưng có thể gặp lỗi vượt quá 7k mã thông báo', + "Bing có thể sử dụng tối đa 7k mã thông báo cho 'ngữ cảnh', mà nó có thể tham khảo trong cuộc trò chuyện. Giới hạn cụ thể không được biết nhưng có thể gặp lỗi vượt quá 7k mã thông báo", com_endpoint_bing_system_message_placeholder: - 'CẢNH BÁO: Sử dụng sai chức năng này có thể bị CẤM sử dụng Bing! Nhấp vào \'Thông điệp hệ thống\' để có hướng dẫn đầy đủ và thông điệp mặc định nếu không có (mặc định là \'Sydney\') được coi là an toàn.', + "CẢNH BÁO: Sử dụng sai chức năng này có thể bị CẤM sử dụng Bing! Nhấp vào 'Thông điệp hệ thống' để có hướng dẫn đầy đủ và thông điệp mặc định nếu không có (mặc định là 'Sydney') được coi là an toàn.", com_endpoint_system_message: 'Thông điệp hệ thống', com_endpoint_default_blank: 'mặc định: trống', com_endpoint_default_false: 'mặc định: sai', @@ -231,7 +242,7 @@ export default { com_endpoint_config_key_edge_instructions: 'hướng dẫn', com_endpoint_config_key_edge_full_key_string: 'để cung cấp chuỗi cookie đầy đủ.', com_endpoint_config_key_chatgpt: - 'Để nhận Mã truy cập của bạn cho ChatGPT \'Phiên bản miễn phí\', đăng nhập vào', + "Để nhận Mã truy cập của bạn cho ChatGPT 'Phiên bản miễn phí', đăng nhập vào", com_endpoint_config_key_chatgpt_then_visit: 'sau đó truy cập', com_endpoint_config_key_chatgpt_copy_token: 'Sao chép mã truy cập.', com_endpoint_config_key_google_need_to: 'Bạn cần', @@ -239,7 +250,7 @@ export default { com_endpoint_config_key_google_vertex_api: 'API trên Google Cloud, sau đó', com_endpoint_config_key_google_service_account: 'Tạo một Tài khoản Dịch vụ', com_endpoint_config_key_google_vertex_api_role: - 'Hãy chắc chắn nhấp vào \'Tạo và Tiếp tục\' để cấp ít nhất vai trò \'Người dùng Vertex AI\' thì còn lại, tạo một khóa JSON để nhập vào đây.', + "Hãy chắc chắn nhấp vào 'Tạo và Tiếp tục' để cấp ít nhất vai trò 'Người dùng Vertex AI' thì còn lại, tạo một khóa JSON để nhập vào đây.", com_nav_auto_scroll: 'Cuộn tự động đến tin nhắn mới nhất khi mở', com_nav_plugin_store: 'Cửa hàng Plugin', com_nav_plugin_search: 'Tìm kiếm plugin', @@ -254,6 +265,12 @@ export default { com_nav_export_recursive_or_sequential: 'Đệ quy hay tuần tự?', com_nav_export_recursive: 'Đệ quy', com_nav_export_conversation: 'Xuất cuộc trò chuyện', + com_nav_export: 'Xuất', + com_nav_shared_links: 'Liên kết được chia sẻ', + com_nav_shared_links_manage: 'Quản l', + com_nav_shared_links_empty: 'Bạn không có link được chia sẻ.', + com_nav_shared_links_name: 'Tên', + com_nav_shared_links_date_shared: 'Ngày chia sẻ', com_nav_theme: 'Chủ đề', com_nav_theme_system: 'Hệ thống', com_nav_theme_dark: 'Tối', @@ -1193,6 +1210,10 @@ export const comparisons = { english: 'Export conversation', translated: 'Xuất cuộc trò chuyện', }, + com_nav_export: { + english: 'Export', + translated: 'Xuất', + }, com_nav_theme: { english: 'Theme', translated: 'Chủ đề', diff --git a/client/src/localization/languages/Zh.ts b/client/src/localization/languages/Zh.ts index e88fc148857..938bbd0d14d 100644 --- a/client/src/localization/languages/Zh.ts +++ b/client/src/localization/languages/Zh.ts @@ -132,6 +132,17 @@ export default { com_ui_assistants_output: '助手输出', com_ui_delete: '删除', com_ui_create: '创建', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete_conversation: '删除对话?', com_ui_delete_conversation_confirm: '这将删除', com_ui_delete_assistant_confirm: '确定要删除此助手吗?该操作无法撤销。', @@ -386,6 +397,12 @@ export default { com_nav_export_recursive_or_sequential: '递归或顺序?', com_nav_export_recursive: '递归', com_nav_export_conversation: '导出对话', + com_nav_export: '导出', + com_nav_shared_links: '共享链接', + com_nav_shared_links_manage: '管理', + com_nav_shared_links_empty: '您没有共享链接。', + com_nav_shared_links_name: '名称', + com_nav_shared_links_date_shared: '共享日期', com_nav_my_files: '我的文件', com_nav_theme: '主题', com_nav_theme_system: '跟随系统设置', @@ -1991,6 +2008,10 @@ export const comparisons = { english: 'Export conversation', translated: '导出对话', }, + com_nav_export: { + english: 'Export', + translated: '导出', + }, com_nav_my_files: { english: 'My Files', translated: '我的文件', diff --git a/client/src/localization/languages/ZhTraditional.ts b/client/src/localization/languages/ZhTraditional.ts index 2331cbb609f..dc464f73f71 100644 --- a/client/src/localization/languages/ZhTraditional.ts +++ b/client/src/localization/languages/ZhTraditional.ts @@ -50,6 +50,17 @@ export default { com_ui_import_conversation_error: '導入對話時發生錯誤', com_ui_confirm_action: '確認操作', com_ui_chats: '對話', + com_ui_share: 'Share', + com_ui_share_link_to_chat: 'Share link to chat', + com_ui_share_error: 'There was an error sharing the chat link', + com_ui_share_create_message: 'Your name and any messages you add after sharing stay private.', + com_ui_share_created_message: + 'A public link to your chat has been created. Manage previously shared chats at any time via Settings.', + com_ui_share_update_message: + 'Your name, custom instructions, and any messages you add after sharing stay private.', + com_ui_share_updated_message: + 'A public link to your chat has been updated. Manage previously shared chats at any time via Settings.', + com_ui_shared_link_not_found: 'Shared link not found', com_ui_delete: '刪除', com_ui_delete_conversation: '刪除對話?', com_ui_delete_conversation_confirm: '這將刪除', @@ -240,6 +251,12 @@ export default { com_nav_export_recursive_or_sequential: '遞迴還是序列?', com_nav_export_recursive: '遞迴', com_nav_export_conversation: '匯出對話', + com_nav_export: '匯出', + com_nav_shared_links: '共享連結', + com_nav_shared_links_manage: '管理', + com_nav_shared_links_empty: '您沒有任何共享連結。', + com_nav_shared_links_name: '名称', + com_nav_shared_links_date_shared: '共享日期', com_nav_theme: '主題', com_nav_theme_system: '跟隨系統設定', com_nav_theme_dark: '深色', @@ -1427,6 +1444,10 @@ export const comparisons = { english: 'Export conversation', translated: '匯出對話', }, + com_nav_export: { + english: 'Export', + translated: '匯出', + }, com_nav_theme: { english: 'Theme', translated: '主題', diff --git a/client/src/routes/ShareRoute.tsx b/client/src/routes/ShareRoute.tsx new file mode 100644 index 00000000000..2965c9cdd40 --- /dev/null +++ b/client/src/routes/ShareRoute.tsx @@ -0,0 +1,10 @@ +import { useParams } from 'react-router-dom'; +import ShareView from '~/components/Share/ShareView'; + +export default function ShareRoute() { + return ( + <> + + + ); +} diff --git a/client/src/routes/index.tsx b/client/src/routes/index.tsx index 58fb524ad03..89576fc47d1 100644 --- a/client/src/routes/index.tsx +++ b/client/src/routes/index.tsx @@ -7,6 +7,7 @@ import { ApiErrorWatcher, } from '~/components/Auth'; import { AuthContextProvider } from '~/hooks/AuthContext'; +import ShareRoute from './ShareRoute'; import ChatRoute from './ChatRoute'; import Search from './Search'; import Root from './Root'; @@ -31,6 +32,10 @@ export const router = createBrowserRouter([ path: 'reset-password', element: , }, + { + path: 'share/:shareId', + element: , + }, { element: , children: [ diff --git a/client/src/utils/collection.ts b/client/src/utils/collection.ts new file mode 100644 index 00000000000..6d8b1c691aa --- /dev/null +++ b/client/src/utils/collection.ts @@ -0,0 +1,70 @@ +import { InfiniteData } from '@tanstack/react-query'; + +export const addData = ( + data: InfiniteData, + collectionName: string, + newData: TData, + findIndex: (page: TCollection) => number, +) => { + const dataJson = JSON.parse(JSON.stringify(data)) as InfiniteData; + const { pageIndex, index } = findPage(data, findIndex); + + if (pageIndex !== -1 && index !== -1) { + return updateData(data, collectionName, newData, findIndex); + } + dataJson.pages[0][collectionName].unshift({ + ...newData, + updatedAt: new Date().toISOString(), + }); + + return dataJson; +}; + +export function findPage(data: InfiniteData, findIndex: (page: TData) => number) { + for (let pageIndex = 0; pageIndex < data.pages.length; pageIndex++) { + const page = data.pages[pageIndex]; + const index = findIndex(page); + if (index !== -1) { + return { pageIndex, index }; + } + } + return { pageIndex: -1, index: -1 }; // Not found +} + +export const updateData = ( + data: InfiniteData, + collectionName: string, + updatedData: TData, + findIndex: (page: TCollection) => number, +) => { + const newData = JSON.parse(JSON.stringify(data)) as InfiniteData; + const { pageIndex, index } = findPage(data, findIndex); + + if (pageIndex !== -1 && index !== -1) { + // Remove the data from its current position + newData.pages[pageIndex][collectionName].splice(index, 1); + // Add the updated data to the top of the first page + newData.pages[0][collectionName].unshift({ + ...updatedData, + updatedAt: new Date().toISOString(), + }); + } + + return newData; +}; + +export const deleteData = ( + data: TData, + collectionName: string, + findIndex: (page: TCollection) => number, +): TData => { + const newData = JSON.parse(JSON.stringify(data)); + const { pageIndex, index } = findPage(newData, findIndex); + + if (pageIndex !== -1 && index !== -1) { + // Delete the data from its current page + newData.pages[pageIndex][collectionName].splice(index, 1); + } + + return newData; +}; diff --git a/client/src/utils/convos.spec.ts b/client/src/utils/convos.spec.ts index 6382ad351fd..be5b60708e1 100644 --- a/client/src/utils/convos.spec.ts +++ b/client/src/utils/convos.spec.ts @@ -146,11 +146,11 @@ describe('Conversation Utilities', () => { }, ], }; - const { pageIndex, convIndex } = findPageForConversation(data as ConversationData, { + const { pageIndex, index } = findPageForConversation(data as ConversationData, { conversationId: '2', }); expect(pageIndex).toBe(0); - expect(convIndex).toBe(1); + expect(index).toBe(1); }); }); }); @@ -219,11 +219,11 @@ describe('Conversation Utilities with Fake Data', () => { describe('findPageForConversation', () => { it('finds the correct page and index for a given conversation in fake data', () => { const targetConversation = convoData.pages[0].conversations[0]; - const { pageIndex, convIndex } = findPageForConversation(convoData, { + const { pageIndex, index } = findPageForConversation(convoData, { conversationId: targetConversation.conversationId as string, }); expect(pageIndex).toBeGreaterThanOrEqual(0); - expect(convIndex).toBeGreaterThanOrEqual(0); + expect(index).toBeGreaterThanOrEqual(0); }); }); }); diff --git a/client/src/utils/convos.ts b/client/src/utils/convos.ts index d572481e2fe..6f152a48510 100644 --- a/client/src/utils/convos.ts +++ b/client/src/utils/convos.ts @@ -14,8 +14,12 @@ import type { ConversationData, ConversationUpdater, GroupedConversations, + ConversationListResponse, } from 'librechat-data-provider'; +import { addData, deleteData, updateData, findPage } from './collection'; +import { InfiniteData } from '@tanstack/react-query'; + export const dateKeys = { today: 'com_ui_date_today', yesterday: 'com_ui_date_yesterday', @@ -105,52 +109,39 @@ export const groupConversationsByDate = (conversations: TConversation[]): Groupe return Object.entries(sortedGroups); }; -export const addConversation: ConversationUpdater = (data, newConversation) => { - const newData = JSON.parse(JSON.stringify(data)) as ConversationData; - const { pageIndex, convIndex } = findPageForConversation(newData, newConversation); - - if (pageIndex !== -1 && convIndex !== -1) { - return updateConversation(data, newConversation); - } - newData.pages[0].conversations.unshift({ - ...newConversation, - updatedAt: new Date().toISOString(), - }); - - return newData; +export const addConversation = ( + data: InfiniteData, + newConversation: TConversation, +): ConversationData => { + return addData( + data, + 'conversations', + newConversation, + (page) => + page.conversations.findIndex((c) => c.conversationId === newConversation.conversationId), + ); }; export function findPageForConversation( data: ConversationData, conversation: TConversation | { conversationId: string }, ) { - for (let pageIndex = 0; pageIndex < data.pages.length; pageIndex++) { - const page = data.pages[pageIndex]; - const convIndex = page.conversations.findIndex( - (c) => c.conversationId === conversation.conversationId, - ); - if (convIndex !== -1) { - return { pageIndex, convIndex }; - } - } - return { pageIndex: -1, convIndex: -1 }; // Not found + return findPage(data, (page) => + page.conversations.findIndex((c) => c.conversationId === conversation.conversationId), + ); } -export const updateConversation: ConversationUpdater = (data, updatedConversation) => { - const newData = JSON.parse(JSON.stringify(data)); - const { pageIndex, convIndex } = findPageForConversation(newData, updatedConversation); - - if (pageIndex !== -1 && convIndex !== -1) { - // Remove the conversation from its current position - newData.pages[pageIndex].conversations.splice(convIndex, 1); - // Add the updated conversation to the top of the first page - newData.pages[0].conversations.unshift({ - ...updatedConversation, - updatedAt: new Date().toISOString(), - }); - } - - return newData; +export const updateConversation = ( + data: InfiniteData, + newConversation: TConversation, +): ConversationData => { + return updateData( + data, + 'conversations', + newConversation, + (page) => + page.conversations.findIndex((c) => c.conversationId === newConversation.conversationId), + ); }; export const updateConvoFields: ConversationUpdater = ( @@ -158,13 +149,13 @@ export const updateConvoFields: ConversationUpdater = ( updatedConversation: Partial & Pick, ): ConversationData => { const newData = JSON.parse(JSON.stringify(data)); - const { pageIndex, convIndex } = findPageForConversation( + const { pageIndex, index } = findPageForConversation( newData, updatedConversation as { conversationId: string }, ); - if (pageIndex !== -1 && convIndex !== -1) { - const deleted = newData.pages[pageIndex].conversations.splice(convIndex, 1); + if (pageIndex !== -1 && index !== -1) { + const deleted = newData.pages[pageIndex].conversations.splice(index, 1); const oldConversation = deleted[0] as TConversation; newData.pages[0].conversations.unshift({ @@ -181,15 +172,9 @@ export const deleteConversation = ( data: ConversationData, conversationId: string, ): ConversationData => { - const newData = JSON.parse(JSON.stringify(data)); - const { pageIndex, convIndex } = findPageForConversation(newData, { conversationId }); - - if (pageIndex !== -1 && convIndex !== -1) { - // Delete the conversation from its current page - newData.pages[pageIndex].conversations.splice(convIndex, 1); - } - - return newData; + return deleteData(data, 'conversations', (page) => + page.conversations.findIndex((c) => c.conversationId === conversationId), + ); }; export const getConversationById = ( diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index 1be9a66a285..ffb7e4fb32a 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -7,6 +7,7 @@ export * from './presets'; export * from './textarea'; export * from './languages'; export * from './endpoints'; +export * from './sharedLink'; export { default as cn } from './cn'; export { default as buildTree } from './buildTree'; export { default as getLoginError } from './getLoginError'; diff --git a/client/src/utils/sharedLink.fakeData.ts b/client/src/utils/sharedLink.fakeData.ts new file mode 100644 index 00000000000..947a26fe2de --- /dev/null +++ b/client/src/utils/sharedLink.fakeData.ts @@ -0,0 +1,955 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import type { SharedLinkListData } from 'librechat-data-provider'; + +const today = new Date(); +today.setDate(today.getDate() - 3); + +export const sharedLinkData: SharedLinkListData = { + pages: [ + { + sharedLinks: [ + { + conversationId: '7a327f49-0850-4741-b5da-35373e751256', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-04T04:31:04.897Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'b3c2e29b131c464182b483c4', + '6dc217152a134ac1826fc46c', + '483658114d104691b2501fbf', + 'cfb8467cfd30438e8268cf92', + ], + shareId: '62f850ad-a0d8-48a5-b439-2d1dbaba291c', + title: 'Test Shared Link 1', + updatedAt: '2024-04-11T11:10:42.329Z', + }, + { + conversationId: '1777ad5f-5e53-4847-be49-86f66c649ac6', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-05T05:59:31.571Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'bc53fda136ba46fb965260b8', + '138b83d659c84250904feb53', + '1c750ffab31546bd85b81360', + '7db87f183e4d489fae0b5161', + '64ee2004479644b7b5ffd2ea', + '4dd2b9a0704c4ae79688292e', + '25394c2bb2ee40feaf67836f', + '838ed537d9054780a3d9f272', + '300728390f8c4021a6c066ca', + 'ea30b637cb8f463192523919', + ], + shareId: '1f43f69f-0562-4129-b181-3c37df0df43e', + title: 'Test Shared Link 2', + updatedAt: '2024-04-16T17:52:40.250Z', + }, + { + conversationId: 'a9682067-a7c9-4375-8efb-6b8fa1c71def', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-03T08:23:35.147Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'bb4fe223548b480eae6d64af', + '420ef02293d0470b96980e7b', + 'ae0ffbb27e13418fbd63e7c2', + '43df3ea55cfb4219b1630518', + 'c4fb3be788404058a4c9780d', + '6ee6a5833b1d4849a95be890', + '0b8a3ecf5ca5449b9bdc0ed8', + 'a3daed97f0e5432a8b6031c0', + '6a7d10c55c9a46cfbd08d6d2', + '216d40fa813a44059bd01ab6', + ], + shareId: 'e84d2642-9b3a-4e20-b92a-11a37eebe33f', + title: 'Test Shared Link 3', + updatedAt: '2024-02-06T04:21:17.065Z', + }, + { + conversationId: 'b61f9a0a-6d5d-4d0e-802b-4c1866428816', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-06T19:25:45.708Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: ['00aad718514044dda8e044ec', '8cb3b67fccd64b8c8ac0abbb'], + shareId: '9011e12a-b2fe-4003-9623-bf1b5f80396b', + title: 'Test Shared Link 4', + updatedAt: '2024-03-21T22:37:32.704Z', + }, + { + conversationId: '4ac3fd9e-437b-4988-b870-29cacf28abef', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-03T15:45:11.220Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '6b05f825ca7747f294f2ac64', + '871ee06fb6a141879ca1cb25', + '47b05821c6134a3b9f21072e', + ], + shareId: '51d3ab25-195e-47d0-a5e3-d0694ece776a', + title: 'Test Shared Link 5', + updatedAt: '2024-04-03T23:20:11.213Z', + }, + { + conversationId: '6ed26de8-3310-4abb-b561-4bdae9400aac', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-22T19:12:14.995Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'ac2929efa82b4cd78aae02d6', + '4266450abc7b41a59887e99d', + '95df3c7c802c40e0b643bb96', + 'f21038af46074e51a2c4bd87', + '3f064bc8589c435786a92bcb', + ], + shareId: 'c3bc13ed-190a-4ffa-8a05-50f8dad3c83e', + title: 'Test Shared Link 6', + updatedAt: '2024-04-25T19:55:25.785Z', + }, + { + conversationId: 'b3c0aaca-ee76-42a2-b53b-5e85baca2f91', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-04T00:37:12.929Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '5a44ebd0bf05418e98cc9e5d', + '88b93127aef74bfb94666ac1', + 'bf654993c34743c9a5a1b76c', + '2514259bd702491e924da475', + '60dbbf91a6734aa081e082cd', + '11efabaa3a8f4df8bf85410b', + '3f5bbf38abdb42efa65a8740', + '5b9dd8246dde41ae9ebd57c4', + ], + shareId: '871d41fe-fb8a-41d4-8460-8bb93fb8aa98', + title: 'Test Shared Link 7', + updatedAt: '2024-03-13T14:34:26.790Z', + }, + { + conversationId: '2071122a-57cc-4f16-baa8-1e8af3e23522', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-01-24T03:22:58.012Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '8c94aad21caa45d6acb863c8', + 'c10e4e0bfe554a94920093ba', + '2e4c2e2238f24f63b08440bc', + '05bacd00320342298f9f439f', + 'c8b7750a7d8a4e2fbdc2630b', + 'a84573fea668476a87207979', + '6ab15a1b96c24798b1bddd6f', + 'b699d8e42324493eae95ca44', + ], + shareId: 'f90f738a-b0ac-4dba-bb39-ad3d77919a21', + title: 'Test Shared Link 8', + updatedAt: '2024-01-22T11:09:51.834Z', + }, + { + conversationId: 'ee06374d-4452-4fbe-a1c0-5dbc327638f9', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-03T19:24:21.281Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '297d0827c81a4da0a881561a', + '3131ef1b3c484542b0db1f92', + 'e8879a50340c49449e970dbc', + 'fe598327a93b4b0399055edd', + 'acc7a2a24e204325befffbcd', + '6ec3c6450e124cbf808c8839', + '714e3443f62045aaaff17f93', + '014be593aaad41cab54a1c44', + ], + shareId: '0fc91bab-083d-449f-add3-1e32146b6c4a', + title: 'Test Shared Link 9', + updatedAt: '2024-03-14T00:52:52.345Z', + }, + { + conversationId: '0d2f6880-cacf-4f7b-930e-35881df1cdea', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-14T03:18:45.587Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: ['1d045c1cf37742a6a979e21b'], + shareId: 'd87deb62-b993-476c-b520-104b08fd7445', + title: 'Test Shared Link 10', + updatedAt: '2024-03-26T18:38:41.222Z', + }, + { + conversationId: '1fe437fd-68f0-4e3e-81a9-ca9a0fa8220a', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-16T19:55:23.412Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'a28b5f27c95e4700bcd158dc', + '6e85f0a8b6ae4107a5819317', + 'fa5b863c91224a0098aebd64', + 'b73811a510e54acebe348371', + 'f3f7f7d7b69a485da727f9c2', + '81d82df3098c4e359d29703f', + ], + shareId: '704a1a9c-5366-4f55-b69e-670a374f4326', + title: 'Test Shared Link 11', + updatedAt: '2024-04-11T05:00:25.349Z', + }, + { + conversationId: '50465c8e-102f-4f94-88c2-9cf607a6c336', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-05T21:57:52.289Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'a64886199ab641c29eb6fdaf', + '9c16497010354cf385d4cc1d', + '36cdeb4d1e4f45078edfe28a', + 'a11f4ea78fa44f57bfc5bfc6', + 'dea42fcfe7a544feb5debc26', + 'ece0d630cd89420ca80ffe25', + '719165a5d80644ae8fae9498', + 'f27111921a10470982f522b2', + '10b78255f7a24b6192e67693', + ], + shareId: 'e47eaf30-c1ed-4cc2-b2b8-8cdec4b1ea2f', + title: 'Test Shared Link 12', + updatedAt: '2024-02-07T15:43:21.110Z', + }, + { + conversationId: '1834f551-0a68-4bc7-a66a-21a234462d24', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-23T02:58:52.653Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'cb5d19d986194f779a6f47fd', + '72159d6668f347f99398aec9', + 'cbe535213d664a6280d9a19e', + '8dccceadcb3a44148962ba47', + ], + shareId: '976b55cb-d305-40f8-ae06-ae516f4e49f5', + title: 'Test Shared Link 13', + updatedAt: '2024-05-02T10:21:05.190Z', + }, + { + conversationId: 'd8175b2f-f7c0-4f61-850d-f524bf8a84df', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-09T09:04:10.576Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '0f3708fc670d46998b1294d5', + '794520b9cee84c23bff01d5a', + 'b05d2af2d37c426a970d8326', + 'bd4239e379284d01acb9aaf4', + 'e6265cfbbd88420781b27248', + '5262193aef7c426cafe2ee85', + '848569e2ca4843beaf64efc4', + '99f3b438241c4454a6784ac2', + '111d346fbeae4806bdf23490', + 'fe4bde34e1a143f1a12fa628', + ], + shareId: '928eb0a8-e0ea-470d-8b6a-92e0981d61b0', + title: 'Test Shared Link 14', + updatedAt: '2024-04-15T18:00:13.094Z', + }, + { + conversationId: '281984c0-fed0-4428-8e50-c7c93cba4ae0', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-23T23:26:41.956Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '7e781fd08408426795c243e7', + '3c6d729fd3524a65a7b2a5e3', + '53bdbec6ee6148e78795d6e1', + '46f8170f28684ccc8ee56f33', + '3350d9aa7c814c89af6d3640', + ], + shareId: '7a251af6-1ad3-4b24-830c-21b38124f325', + title: 'Test Shared Link 15', + updatedAt: '2024-03-18T16:33:35.498Z', + }, + { + conversationId: '09610b11-6087-4d15-b163-e1bc958f2e82', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-05T20:00:36.159Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: ['6dce61720af24c70926efe87'], + shareId: '2b389d5e-eb24-4b29-a8e1-c0545cdfa1fc', + title: 'Test Shared Link 16', + updatedAt: '2024-02-23T05:49:50.020Z', + }, + { + conversationId: '0c388322-905c-4c57-948c-1ba9614fdc2f', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-05T00:03:20.078Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'e3755ff2cf9f403c9d20901f', + 'e32733b8da1440ec9d9dc2df', + 'e2870d0361634d4f867e1e57', + '2e504afb8675434bb9f58cb5', + 'ea38d76735c54f94bf378ed3', + '8712cda1bfc8480eba6c65aa', + '3f43a655706f4032a9e1efb4', + '3f890f8279f4436da2a7d767', + '4ca7616c04404391a7cfc94f', + 'd3e176a831ff48e49debabce', + ], + shareId: '2866400b-bcb9-43a4-8cbf-6597959f8c55', + title: 'Test Shared Link 17', + updatedAt: '2024-03-16T02:53:06.642Z', + }, + { + conversationId: '5ac2b90a-63f8-4388-919b-40a1c1fea874', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-01-21T15:30:37.893Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '734a0427c6224fca87e2a89d', + '6af13387ddf0495d9c6ebad9', + '02a93d5659f343678b12b932', + '8af2f028c5114286a3339075', + '3a8bec13fc574fb9a9f938e2', + '6f4aa482286548b7b42668e6', + 'c1d4f94a2eaf4e44b94c5834', + '442d9491b51d49fcab60366d', + '82a115a84b2a4457942ca6cf', + '152d8c2894a0454d9248c9f5', + ], + shareId: 'e76f6a90-06f3-4846-8e3d-987d37af27b5', + title: 'Test Shared Link 18', + updatedAt: '2024-01-27T06:25:27.032Z', + }, + { + conversationId: '01521fef-aa0b-4670-857d-f19bfc0ce664', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-01T21:46:40.674Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '222cf562d8e24b1b954395c2', + 'c6f299f588c24905b771e623', + 'f023f30fd4d9472c9bf60b84', + 'e4929e3f14d748a18656f1be', + 'a01f453fcb0a49b5b488a22c', + '4ceee6b365ab4386bacb4d27', + 'c2cab81da0be4c6e97f11f92', + '644c32d10f2f4e2086d5e04d', + '5225d1286db14cc6a47fdea5', + 'c821ebb220ae495b98f2e17f', + ], + shareId: '1b2d8bf5-ff90-478a-bdf6-ea622fb4875a', + title: 'Test Shared Link 19', + updatedAt: '2024-02-25T15:52:56.189Z', + }, + { + conversationId: '54f5d332-efc7-4062-9e1d-c70c3dbbc964', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-01-29T15:57:22.808Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '49771038e2dd4de0a28b19f2', + '0debd4ad13de4db9a65fe589', + 'a9c8e6e34c34486ca27b7c88', + 'd7b0ace0438146789e8b1899', + ], + shareId: '4f5eea7d-b3a8-4b72-ad1e-a4d516c582c2', + title: 'Test Shared Link 20', + updatedAt: '2024-03-18T13:12:10.828Z', + }, + { + conversationId: '99dabf25-46a5-43bb-8274-715c64e56032', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-05T03:35:11.327Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: ['965a55515727404eb70dec8f'], + shareId: '2360b7c1-20d7-46b9-919d-65576a899ab9', + title: 'Test Shared Link 21', + updatedAt: '2024-04-17T11:22:12.800Z', + }, + { + conversationId: '1e2ffc1a-3546-460e-819c-689eb88940c6', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-22T08:40:32.663Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '131f4b03ad3d4e90803a203d', + '7f55262c554f4d97a8fef0ec', + '341e8fea28e241fc8b5a2398', + ], + shareId: 'f3e370ed-420c-4579-a033-e18743b49485', + title: 'Test Shared Link 22', + updatedAt: '2024-04-07T22:06:07.162Z', + }, + { + conversationId: '14510a0c-01cc-4bfb-8336-3388573ac4d8', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-08T08:20:28.619Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '022f87b1bf0d4e4688970daa', + '42519e8f3603496faae0969c', + 'abc29ac88d66485aa11e4b58', + ], + shareId: '0f46f1fd-95d3-4a6f-a5aa-ae5338dc5337', + title: 'Test Shared Link 23', + updatedAt: '2024-03-06T12:05:33.679Z', + }, + { + conversationId: '2475594e-10dc-4d6a-aa58-5ce727a36271', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-04T07:43:46.952Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '5d0cd8bef4c241aba5d822a8', + 'a19669a364d84ab5bbafbe0c', + '336686022ea6456b9a63879d', + '3323c9b85acc4ffba35aad04', + 'bf15e8860a01474cb4744842', + '5a055eb825ed4173910fffd5', + '36a5e683ad144ec68c2a8ce0', + '8bc1d5590a594fa1afc18ee1', + 'f86444b60bea437ba0d0ef8e', + '5be768788d984723aef5c9a0', + ], + shareId: 'b742f35c-e6a3-4fa4-b35d-abab4528d7d6', + title: 'Test Shared Link 24', + updatedAt: '2024-03-27T15:31:10.930Z', + }, + { + conversationId: 'ddb5a61c-82fe-4cc7-a2b0-c34b8c393b28', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-15T02:06:45.901Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '059d7ae5405a42af9c52171d', + '303efd2e676e4fe7aa9fa9d0', + '9f459c2e6a23411ea4a3e153', + '6036a3785adc4b7caa7ea22b', + '65251979d0c64d1f8821b3d9', + '25fdeb5ed99d42cca3041e08', + '61baa25e4e3d42a3aefd6c16', + '91dc4578fee749aeb352b5ea', + 'd52daca5afb84e7890d5d9ad', + ], + shareId: '13106e5f-1b5f-4ed4-963d-790e61c1f4c8', + title: 'Test Shared Link 25', + updatedAt: '2024-02-05T08:39:45.847Z', + }, + { + conversationId: 'df09c89b-0b0d-429c-9c93-b5f4d51ef1ec', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-28T07:50:10.724Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '7f007af305ee4f2197c125d3', + '318b26abfe864dbf9f557bf9', + '0c4709b489ac4211b9f22874', + '8940f9ab45f44b56911819d5', + 'b47ec3aa0cf7413fa446f19b', + '3857f85f492f4e11aa0ea377', + ], + shareId: '31bbafa4-2051-4a20-883b-2f8557c46116', + title: 'Test Shared Link 26', + updatedAt: '2024-02-01T19:52:32.986Z', + }, + { + conversationId: '856a4d54-54f7-483f-9b4e-7b798920be25', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-14T08:57:03.592Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'b5afc1f3569d44378bc7539d', + 'e54804329577443d8685d3b1', + '7b10204ad48c464aac2b752a', + '8e96d562d33b4d6b85f2269e', + 'cd844644f15d4dbdb5772a3b', + '91f5159278ca420c8a0097b2', + '5f8cf34736df4cca962635c1', + '96e2169ddcf5408fb793aeb6', + '988d96959afb4ec08cd3cec4', + '173398cdf05d4838aeb5ad9f', + ], + shareId: '88c159a0-0273-4798-9d21-f95bd650bd30', + title: 'Test Shared Link 27', + updatedAt: '2024-05-08T20:07:46.345Z', + }, + { + conversationId: '41ee3f3f-36a5-4139-993a-1c4d7d055ccb', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-26T10:08:29.943Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: ['883cc3286240405ba558f322', '7ca7809f471e481fa9944795'], + shareId: '97dc26aa-c909-4a9c-91be-b605d25b9cf3', + title: 'Test Shared Link 28', + updatedAt: '2024-04-06T17:36:05.767Z', + }, + { + conversationId: '79e30f91-9b87-484c-8a12-6e4c6e8973d4', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-05-07T05:28:58.595Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'a8ac347785504b51bdad7ea7', + 'ce85321aecf64355b0362f8c', + '21a462895f37474d8d6acdfd', + '095d9104011e4534bda93294', + '503b6e27677c457289366a8d', + '1738d52a60004c9ba6f0c9ec', + 'a157fe44a67f4882a507941b', + '40e30dc275394eb4b9921db0', + 'f4ed9f2fb08640fcbacaa6a7', + 'bbac358328864dc2bfaa39da', + ], + shareId: 'aa36fc45-2a73-4fa2-a500-2a9148fca67d', + title: 'Test Shared Link 29', + updatedAt: '2024-01-26T16:45:59.269Z', + }, + { + conversationId: 'f5eaa000-3657-43d4-bc55-538108723b83', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-22T15:51:31.330Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'a87cfce565844b4ba9230dc5', + '426723bc4c22425e9bdf4b7b', + '73be5795469a444b8f1eca88', + '75a87212574a4cfc80d7d4e3', + '80f982dfc3e94535aed6e7d4', + '86d036c912c142ca8ec0f45a', + 'e3435fbbd4d2443eba30e97d', + 'e451e124aa964398b596af5d', + '1a13913f55e9442e8b5d7816', + ], + shareId: 'fe0f7ea2-74d2-40ba-acb2-437e61fc3bef', + title: 'Test Shared Link 30', + updatedAt: '2024-02-27T13:29:04.060Z', + }, + { + conversationId: 'a1ad92b4-6fac-44be-bad6-7648aeeba7af', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-10T09:32:22.242Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '5931cd94fbcd4fbcbaa20b91', + '0bc5f38ccc4f4b88afa42aed', + '7b4375d65f3f4524a79cb5f0', + 'd2ce098360ce4d19b6961017', + '847f5ee8d2df49a0ba1fd8a7', + '6164a71770c745ea8142a37c', + 'e98a0f1e15c846ac9b113608', + '5297d7df09b44d088cf80da5', + '62260b3f62ba423aa5c1962c', + '21fffc89d1d54e0190819384', + ], + shareId: 'ee5ae35d-540d-4a01-a938-ee7ee97b15ce', + title: 'Test Shared Link 31', + updatedAt: '2024-02-26T03:37:24.862Z', + }, + { + conversationId: '1e502d46-c710-4848-9bf2-674c08e51d9c', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-09T08:37:01.082Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'adb4bfb7657d4d7d92e82edf', + '70bdd81466e0408399b415d3', + 'ef99511981dc4c3baa18d372', + ], + shareId: 'b4fd8b63-7265-4825-89a4-9cebcbaadeee', + title: 'Test Shared Link 32', + updatedAt: '2024-02-27T04:32:40.654Z', + }, + { + conversationId: 'd1a43c39-f05e-4c6e-a8c2-0fcca9cb8928', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-26T15:03:25.546Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '07c070ab8a7541fea96b131c', + 'eb89cc57bcbb47ecb497cd5f', + '651999e46e734837b24c2500', + '608f9fbbbbb645e6b32d7d46', + ], + shareId: '5a4cf7d0-0abb-48c1-8e70-9f4ee3220dc4', + title: 'Test Shared Link 33', + updatedAt: '2024-04-06T21:39:51.521Z', + }, + { + conversationId: 'e549be2b-2623-42a3-8315-a8e35a7776b3', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-23T21:40:32.151Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'aa4e0b65650544589afb5961', + '160841178c0944e88de83956', + '234ac16af26d48a7875ee643', + ], + shareId: 'b083f048-2803-407e-b54a-89261db87ade', + title: 'Test Shared Link 34', + updatedAt: '2024-03-14T12:16:32.984Z', + }, + { + conversationId: '39f415ea-48f2-4bb2-b6f8-c2cf2d5fe42a', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-08T19:02:27.141Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'c77e79bb92b64d36a72d5a4d', + 'ea236310a9ba4b27a2217f09', + 'b25c46f2d23542f6b9d94de9', + ], + shareId: 'a9871169-7012-4206-b35c-7d207309a0f5', + title: 'Test Shared Link 35', + updatedAt: '2024-04-21T04:00:58.151Z', + }, + { + conversationId: 'c0d00265-12c4-45d0-a8bd-95d6e1bda769', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-14T09:50:55.476Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '63cdf78acd0449cf90237b29', + 'b93d82d7612b49fc98f0c211', + 'e56afe7e6e1e478d858a96d0', + '09344c8d22e74ce9b1d615cc', + ], + shareId: 'aa1262ab-54c9-406a-a97f-e2636266cf3e', + title: 'Test Shared Link 36', + updatedAt: '2024-03-24T15:53:36.021Z', + }, + { + conversationId: '5114b13d-8050-4e29-a2fd-85c776aec055', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-01-20T20:39:54.322Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'd397dc5c136a4c7da44e2fb9', + 'a3cd29243629450b87852a85', + '9dd1e0e918844a37ba8dc955', + 'ec2a73f7efe344fe85709c22', + '4d4702651869476b8ae397fd', + '8447430fd4f34aab82921018', + '8d804ee086734d6192b59995', + '29d6ccba37234bb8bd280977', + '31ec4f8c28cc4c21828ecef8', + '8ea630045b5847ec92651f4a', + ], + shareId: '2021fcab-7000-4840-9a8c-f0a1cb1ce8fa', + title: 'Test Shared Link 37', + updatedAt: '2024-04-08T02:09:33.732Z', + }, + { + conversationId: 'afa796fe-c8c1-411d-98d1-a8c8c8550412', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-01-16T23:58:11.179Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '8f54ee5871494f1b9f13f314', + '7778849398db40eb950952fb', + '65977e5d9e12445cb1cd9a54', + '8dba76884b09490a91b1aff9', + '2f6cc465171742b8a529daa3', + '1775b24fe2e94cd89dd6164e', + '780d980e59274240837c0bff', + ], + shareId: '9bb78460-0a26-4df7-be54-99b904b8084a', + title: 'Test Shared Link 38', + updatedAt: '2024-04-22T00:33:47.525Z', + }, + { + conversationId: 'c70fc447-acfc-4b57-84aa-2d8abcc3c5a5', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-24T11:39:14.696Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'b659ff86f9284ae1a40bee94', + '35bce7b6b2124db491f116c4', + 'cf0bad6c2623413babb33e65', + '26c6ce4d46614c86941d5429', + 'fba6517fc3434c188d8e1471', + '3e37398cc2ea4e50920d6271', + 'fd8584b1cf8145c88697b89d', + '8e433df0ada34e2280d4bd91', + 'fc52f80a6df24df5baccb657', + '95cdf9b05b8f4a81a70a37e9', + ], + shareId: '0664b078-8f29-41ff-8c1c-85172c659195', + title: 'Test Shared Link 39', + updatedAt: '2024-03-29T10:22:50.815Z', + }, + { + conversationId: '32ccaa36-cc46-4c84-888d-a86bf9b1d79c', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-04T03:13:19.399Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '8a0cfa8f5e874cf089f91b2e', + 'e9a72907ac9b4e88a8cfa737', + 'aa328aaf978944e18727a967', + '8786577a76b24415920d87a0', + 'ee05127d35ec415a85554406', + ], + shareId: 'a0018d28-52a8-4d31-8884-037cf9037eb7', + title: 'Test Shared Link 40', + updatedAt: '2024-01-30T03:26:15.920Z', + }, + { + conversationId: '2d8f1f40-b0e8-4629-937a-dee5036cb0bb', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-18T15:32:59.697Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '64475ed4f6234326a1104ca2', + 'db0db3ee92e14afaba6db75b', + '1f28a30501a94e3d896c261b', + 'de2eb08823db401d8262d3f3', + '254c32efae97476b954d8dc4', + 'dda42e4e74144cb69e395392', + '85bfe89de9e643fb8d5fa8ff', + '2f52e060a8b645928d0bf594', + ], + shareId: '9740b59b-cd84-461d-9fd7-2e1903b844b2', + title: 'Test Shared Link 41', + updatedAt: '2024-04-23T15:48:54.690Z', + }, + { + conversationId: '5180353f-23a9-48af-8ed0-b05983ef87d1', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-15T10:45:51.373Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '012b97e2df45475b93ad1e37', + '23d5042117a142f5a12762d5', + '8eb8cbca953d4ec18108f6d8', + 'ba506914203442339cd81d25', + '88c3b40cd0ae43d2b670ee41', + '0dd8fe241f5c4ea88730652c', + '80e3d1d7c26c489c9c8741fe', + '317a47a138c6499db73679f0', + '6497260d6a174f799cb56fd5', + ], + shareId: 'a6eaf23e-6e99-4e96-8222-82149c48803b', + title: 'Test Shared Link 42', + updatedAt: '2024-02-24T12:08:27.344Z', + }, + { + conversationId: 'cf3f2919-1840-4f6a-b350-f73f02ba6e90', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-02-14T06:20:45.439Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'ba3b939f8a3443f99f37b296', + 'b2039c988b3841c6b4ccb436', + '89ea6e1d4b3f440bb867d740', + '270210838a724aeb87e9bbe9', + '02dd6b2f185247d9888d5be1', + '6458fe13ee1c470ba33fb931', + ], + shareId: '765042c0-144d-4f7b-9953-0553ed438717', + title: 'Test Shared Link 43', + updatedAt: '2024-04-11T05:23:05.750Z', + }, + { + conversationId: '8efb71ee-7984-409a-b27c-aeb2650d78ba', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-01-28T16:41:04.100Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'cc60d9e2cbb7494481138833', + '1fb8d220b888475ba6c59fd3', + '5fd97817ab25451bb7ac22f5', + '9e8f7765a1bc4ab495da9081', + '4d5997d3c8744aaeb8c96964', + 'd438acb0f7704201857d6916', + 'b5106745d89f4a3fada8cd11', + '3b41562ce727411a83f44cdf', + '627f8f77feb843848145fc5f', + '6bee635eb10443ae9eef20ab', + ], + shareId: 'ed0fe440-479d-4c79-a494-0f461612c474', + title: 'Test Shared Link 44', + updatedAt: '2024-04-15T12:41:00.324Z', + }, + { + conversationId: '7cdd42a6-67bb-48c8-b8c3-bb55cbaa3905', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-24T23:13:42.892Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'a944f461ca094d1c80bea677', + 'bd4b516b51a84285846343b4', + '442f6b4c27f647199279e49c', + 'e672974b3cf74cd3b85537f9', + ], + shareId: '9439972e-226c-4386-910a-e629eb7019c3', + title: 'Test Shared Link 45', + updatedAt: '2024-01-17T07:42:21.103Z', + }, + { + conversationId: '595bab25-e5c1-4bd0-99c1-a099391adb87', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-13T05:58:33.171Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'c39942615fdf435cb22369b5', + '0ec24a7328424a78b7dcecaf', + '335373a769fd43a5833eac16', + '22905090a44f4bf8b6f415f8', + ], + shareId: '18501e23-3fc5-436d-a9aa-ccde7c5c9074', + title: 'Test Shared Link 46', + updatedAt: '2024-02-05T04:34:42.323Z', + }, + { + conversationId: '822a650b-2971-441a-9cb0-b2ecabf7b3ba', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-20T10:29:20.771Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'ed566d1ffd51494e9a069f32', + '3ca9a8bbfb7c43e49e4898d7', + '6534186966784f0fba45c1ab', + '8a9e394dda8542d4a4db1140', + '002d883a1c344de0beb794b3', + '61e9e872aa854288a4ac9694', + '11e465cb875746aaa5894327', + 'ead6b00c855f4907ac5070af', + ], + shareId: 'aaaf89e4-eb3d-45f8-9e24-f370d777d8f7', + title: 'Test Shared Link 47', + updatedAt: '2024-04-29T03:11:47.109Z', + }, + { + conversationId: 'ce68ce26-07fc-4448-9239-f1925cfaaa72', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-03-15T15:04:08.691Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + 'a1851d231ee748e59ed43494', + '363372c828d8443b81abffd4', + '0b2e97210bd14e229ddb6641', + ], + shareId: 'f4de7c43-c058-43f5-bdab-0854d939dfb9', + title: 'Test Shared Link 48', + updatedAt: '2024-03-05T11:43:00.177Z', + }, + { + conversationId: '3fafe417-b5f8-4cc8-ac8e-897ebef836bd', + user: '662dbe728ca96f444c6f69f4', + createdAt: '2024-04-20T05:34:57.880Z', + isAnonymous: true, + isPublic: true, + isVisible: true, + messages: [ + '876337c495ca40c080b65c1d', + 'b5e914ac15ff439a9836a9ea', + 'cb6379d0a9ad442291d78c14', + '529424b650a4478ba012cf40', + '99ff1ed49cb2483bbd970730', + '0f0e215e179f4cfba56c7b03', + '210940fbe4c745d183358ed1', + '99246c796c7a44c2ae85a549', + 'a2b967556867499eb437674a', + ], + shareId: '79ec3716-ea2e-4045-8a82-056d63ebc939', + title: 'Test Shared Link 49', + updatedAt: '2024-03-19T08:01:13.445Z', + }, + ], + pages: 49, + pageNumber: '1', + pageSize: 25, + }, + ], + pageParams: [null], +}; diff --git a/client/src/utils/sharedLink.spec.ts b/client/src/utils/sharedLink.spec.ts new file mode 100644 index 00000000000..1ead5ee2bec --- /dev/null +++ b/client/src/utils/sharedLink.spec.ts @@ -0,0 +1,149 @@ +import { sharedLinkData } from './sharedLink.fakeData'; +import { addSharedLink, updateSharedLink, deleteSharedLink } from './sharedLink'; + +import type { TSharedLink, SharedLinkListData } from 'librechat-data-provider'; + +describe('Shared Link Utilities', () => { + describe('addSharedLink', () => { + it('adds a new shared link to the top of the list', () => { + const data = { pages: [{ sharedLinks: [] }] }; + const newSharedLink = { shareId: 'new', updatedAt: '2023-04-02T12:00:00Z' }; + const newData = addSharedLink( + data as unknown as SharedLinkListData, + newSharedLink as TSharedLink, + ); + expect(newData.pages[0].sharedLinks).toHaveLength(1); + expect(newData.pages[0].sharedLinks[0].shareId).toBe('new'); + }); + it('does not add a shared link but updates it if it already exists', () => { + const data = { + pages: [ + { + sharedLinks: [ + { shareId: '1', updatedAt: '2023-04-01T12:00:00Z' }, + { shareId: '2', updatedAt: '2023-04-01T13:00:00Z' }, + ], + }, + ], + }; + const newSharedLink = { shareId: '2', updatedAt: '2023-04-02T12:00:00Z' }; + const newData = addSharedLink( + data as unknown as SharedLinkListData, + newSharedLink as TSharedLink, + ); + expect(newData.pages[0].sharedLinks).toHaveLength(2); + expect(newData.pages[0].sharedLinks[0].shareId).toBe('2'); + }); + }); + + describe('updateSharedLink', () => { + it('updates an existing shared link and moves it to the top', () => { + const initialData = { + pages: [ + { + sharedLinks: [ + { shareId: '1', updatedAt: '2023-04-01T12:00:00Z' }, + { shareId: '2', updatedAt: '2023-04-01T13:00:00Z' }, + ], + }, + ], + }; + const updatedSharedLink = { shareId: '1', updatedAt: '2023-04-02T12:00:00Z' }; + const newData = updateSharedLink( + initialData as unknown as SharedLinkListData, + updatedSharedLink as TSharedLink, + ); + expect(newData.pages[0].sharedLinks).toHaveLength(2); + expect(newData.pages[0].sharedLinks[0].shareId).toBe('1'); + }); + it('does not update a shared link if it does not exist', () => { + const initialData = { + pages: [ + { + sharedLinks: [ + { shareId: '1', updatedAt: '2023-04-01T12:00:00Z' }, + { shareId: '2', updatedAt: '2023-04-01T13:00:00Z' }, + ], + }, + ], + }; + const updatedSharedLink = { shareId: '3', updatedAt: '2023-04-02T12:00:00Z' }; + const newData = updateSharedLink( + initialData as unknown as SharedLinkListData, + updatedSharedLink as TSharedLink, + ); + expect(newData.pages[0].sharedLinks).toHaveLength(2); + expect(newData.pages[0].sharedLinks[0].shareId).toBe('1'); + }); + }); + + describe('deleteSharedLink', () => { + it('removes a shared link by id', () => { + const initialData = { + pages: [ + { + sharedLinks: [ + { shareId: '1', updatedAt: '2023-04-01T12:00:00Z' }, + { shareId: '2', updatedAt: '2023-04-01T13:00:00Z' }, + ], + }, + ], + }; + const newData = deleteSharedLink(initialData as unknown as SharedLinkListData, '1'); + expect(newData.pages[0].sharedLinks).toHaveLength(1); + expect(newData.pages[0].sharedLinks[0].shareId).not.toBe('1'); + }); + + it('does not remove a shared link if it does not exist', () => { + const initialData = { + pages: [ + { + sharedLinks: [ + { shareId: '1', updatedAt: '2023-04-01T12:00:00Z' }, + { shareId: '2', updatedAt: '2023-04-01T13:00:00Z' }, + ], + }, + ], + }; + const newData = deleteSharedLink(initialData as unknown as SharedLinkListData, '3'); + expect(newData.pages[0].sharedLinks).toHaveLength(2); + }); + }); +}); + +describe('Shared Link Utilities with Fake Data', () => { + describe('addSharedLink', () => { + it('adds a new shared link to the existing fake data', () => { + const newSharedLink = { + shareId: 'new', + updatedAt: new Date().toISOString(), + } as TSharedLink; + const initialLength = sharedLinkData.pages[0].sharedLinks.length; + const newData = addSharedLink(sharedLinkData, newSharedLink); + expect(newData.pages[0].sharedLinks.length).toBe(initialLength + 1); + expect(newData.pages[0].sharedLinks[0].shareId).toBe('new'); + }); + }); + + describe('updateSharedLink', () => { + it('updates an existing shared link within fake data', () => { + const updatedSharedLink = { + ...sharedLinkData.pages[0].sharedLinks[0], + title: 'Updated Title', + }; + const newData = updateSharedLink(sharedLinkData, updatedSharedLink); + expect(newData.pages[0].sharedLinks[0].title).toBe('Updated Title'); + }); + }); + + describe('deleteSharedLink', () => { + it('removes a shared link by id from fake data', () => { + const shareIdToDelete = sharedLinkData.pages[0].sharedLinks[0].shareId as string; + const newData = deleteSharedLink(sharedLinkData, shareIdToDelete); + const deletedDataExists = newData.pages[0].sharedLinks.some( + (c) => c.shareId === shareIdToDelete, + ); + expect(deletedDataExists).toBe(false); + }); + }); +}); diff --git a/client/src/utils/sharedLink.ts b/client/src/utils/sharedLink.ts new file mode 100644 index 00000000000..4eb2a7bc995 --- /dev/null +++ b/client/src/utils/sharedLink.ts @@ -0,0 +1,30 @@ +import { SharedLinkListData, SharedLinkListResponse, TSharedLink } from 'librechat-data-provider'; +import { addData, deleteData, updateData } from './collection'; +import { InfiniteData } from '@tanstack/react-query'; + +export const addSharedLink = ( + data: InfiniteData, + newSharedLink: TSharedLink, +): SharedLinkListData => { + return addData(data, 'sharedLinks', newSharedLink, (page) => + page.sharedLinks.findIndex((c) => c.shareId === newSharedLink.shareId), + ); +}; + +export const updateSharedLink = ( + data: InfiniteData, + newSharedLink: TSharedLink, +): SharedLinkListData => { + return updateData( + data, + 'sharedLinks', + newSharedLink, + (page) => page.sharedLinks.findIndex((c) => c.shareId === newSharedLink.shareId), + ); +}; + +export const deleteSharedLink = (data: SharedLinkListData, shareId: string): SharedLinkListData => { + return deleteData(data, 'sharedLinks', (page) => + page.sharedLinks.findIndex((c) => c.shareId === shareId), + ); +}; diff --git a/packages/data-provider/src/api-endpoints.ts b/packages/data-provider/src/api-endpoints.ts index 2e56efb8d0f..e1fdd55fb06 100644 --- a/packages/data-provider/src/api-endpoints.ts +++ b/packages/data-provider/src/api-endpoints.ts @@ -7,6 +7,13 @@ export const userPlugins = () => '/api/user/plugins'; export const messages = (conversationId: string, messageId?: string) => `/api/messages/${conversationId}${messageId ? `/${messageId}` : ''}`; +const shareRoot = '/api/share'; +export const shareMessages = (shareId: string) => `${shareRoot}/${shareId}`; +export const getSharedLinks = (pageNumber: string, isPublic: boolean) => + `${shareRoot}?pageNumber=${pageNumber}&isPublic=${isPublic}`; +export const createSharedLink = shareRoot; +export const updateSharedLink = shareRoot; + const keysEndpoint = '/api/keys'; export const keys = () => keysEndpoint; diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index b3346190963..55fe8f459b1 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -31,6 +31,34 @@ export function getMessagesByConvoId(conversationId: string): Promise { + return request.get(endpoints.shareMessages(shareId)); +} + +export const listSharedLinks = ( + params?: q.SharedLinkListParams, +): Promise => { + const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided + const isPublic = params?.isPublic || true; // Default to true if not provided + return request.get(endpoints.getSharedLinks(pageNumber, isPublic)); +}; + +export function getSharedLink(shareId: string): Promise { + return request.get(endpoints.shareMessages(shareId)); +} + +export function createSharedLink(payload: t.TSharedLinkRequest): Promise { + return request.post(endpoints.createSharedLink, payload); +} + +export function updateSharedLink(payload: t.TSharedLinkRequest): Promise { + return request.patch(endpoints.updateSharedLink, payload); +} + +export function deleteSharedLink(shareId: string): Promise { + return request.delete(endpoints.shareMessages(shareId)); +} + export function updateMessage(payload: t.TUpdateMessageRequest): Promise { const { conversationId, messageId, text } = payload; if (!conversationId) { diff --git a/packages/data-provider/src/keys.ts b/packages/data-provider/src/keys.ts index aa664f8c39a..13f894ac8ad 100644 --- a/packages/data-provider/src/keys.ts +++ b/packages/data-provider/src/keys.ts @@ -1,5 +1,7 @@ export enum QueryKeys { messages = 'messages', + sharedMessages = 'sharedMessages', + sharedLinks = 'sharedLinks', allConversations = 'allConversations', archivedConversations = 'archivedConversations', searchConversations = 'searchConversations', diff --git a/packages/data-provider/src/react-query/react-query-service.ts b/packages/data-provider/src/react-query/react-query-service.ts index bb898939ea1..0dea58a97ae 100644 --- a/packages/data-provider/src/react-query/react-query-service.ts +++ b/packages/data-provider/src/react-query/react-query-service.ts @@ -14,6 +14,7 @@ import { QueryKeys } from '../keys'; import request from '../request'; import * as s from '../schemas'; import * as t from '../types'; +import { AxiosError } from 'axios'; export const useAbortRequestWithMessage = (): UseMutationResult< void, @@ -60,6 +61,22 @@ export const useGetMessagesByConvoId = ( ); }; +export const useGetSharedMessages = ( + shareId: string, + config?: UseQueryOptions, +): QueryObserverResult => { + return useQuery( + [QueryKeys.sharedMessages, shareId], + () => dataService.getSharedMessages(shareId), + { + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + ...config, + }, + ); +}; + export const useGetUserBalance = ( config?: UseQueryOptions, ): QueryObserverResult => { @@ -398,14 +415,17 @@ export const useUpdateUserPluginsMutation = (): UseMutationResult< }); }; -export const useGetStartupConfig = (): QueryObserverResult => { - return useQuery( +export const useGetStartupConfig = ( + config?: UseQueryOptions, +): QueryObserverResult => { + return useQuery( [QueryKeys.startupConfig], () => dataService.getStartupConfig(), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, + ...config, }, ); }; diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 53f8535f100..942b2f8fd1e 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -381,6 +381,19 @@ export type TConversation = z.infer & { presetOverride?: Partial; }; +export const tSharedLinkSchema = z.object({ + conversationId: z.string(), + shareId: z.string(), + messages: z.array(z.string()), + isAnonymous: z.boolean(), + isPublic: z.boolean(), + isVisible: z.boolean(), + title: z.string(), + createdAt: z.string(), + updatedAt: z.string(), +}); +export type TSharedLink = z.infer; + export const openAISchema = tConversationSchema .pick({ model: true, diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 895b763fe6e..5efdece1623 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -1,5 +1,12 @@ import OpenAI from 'openai'; -import type { TResPlugin, TMessage, TConversation, EModelEndpoint, ImageDetail } from './schemas'; +import type { + TResPlugin, + TMessage, + TConversation, + EModelEndpoint, + ImageDetail, + TSharedLink, +} from './schemas'; import type { TSpecsConfig } from './models'; export type TOpenAIMessage = OpenAI.Chat.ChatCompletionMessageParam; export type TOpenAIFunction = OpenAI.Chat.ChatCompletionCreateParams.Function; @@ -133,6 +140,19 @@ export type TArchiveConversationRequest = { export type TArchiveConversationResponse = TConversation; +export type TSharedMessagesResponse = Omit & { + messages: TMessage[]; +}; +export type TSharedLinkRequest = Partial< + Omit +> & { + conversationId: string; +}; + +export type TSharedLinkResponse = TSharedLink; +export type TSharedLinksResponse = TSharedLink[]; +export type TDeleteSharedLinkResponse = TSharedLink; + export type TForkConvoRequest = { messageId: string; conversationId: string; diff --git a/packages/data-provider/src/types/mutations.ts b/packages/data-provider/src/types/mutations.ts index 57b65ab5756..421d0dd42cf 100644 --- a/packages/data-provider/src/types/mutations.ts +++ b/packages/data-provider/src/types/mutations.ts @@ -90,3 +90,13 @@ export type DeleteConversationOptions = MutationOptions< >; export type ForkConvoOptions = MutationOptions; + +export type CreateSharedLinkOptions = MutationOptions< + types.TSharedLink, + Partial +>; +export type UpdateSharedLinkOptions = MutationOptions< + types.TSharedLink, + Partial +>; +export type DeleteSharedLinkOptions = MutationOptions; diff --git a/packages/data-provider/src/types/queries.ts b/packages/data-provider/src/types/queries.ts index 18e56ecca1e..ad4c4db89e9 100644 --- a/packages/data-provider/src/types/queries.ts +++ b/packages/data-provider/src/types/queries.ts @@ -1,5 +1,5 @@ import type { InfiniteData } from '@tanstack/react-query'; -import type { TMessage, TConversation } from '../schemas'; +import type { TMessage, TConversation, TSharedLink } from '../schemas'; export type Conversation = { id: string; createdAt: number; @@ -16,7 +16,7 @@ export type ConversationListParams = { order?: 'asc' | 'desc'; pageNumber: string; // Add this line conversationId?: string; - isArchived: boolean; + isArchived?: boolean; }; // Type for the response from the conversation list API @@ -33,3 +33,24 @@ export type ConversationUpdater = ( data: ConversationData, conversation: TConversation, ) => ConversationData; + +export type SharedMessagesResponse = Omit & { + messages: TMessage[]; +}; +export type SharedLinkListParams = Omit & { + isPublic?: boolean; +}; + +export type SharedLinksResponse = Omit & { + sharedLinks: TSharedLink[]; +}; + +// Type for the response from the conversation list API +export type SharedLinkListResponse = { + sharedLinks: TSharedLink[]; + pageNumber: string; + pageSize: string | number; + pages: string | number; +}; + +export type SharedLinkListData = InfiniteData;