-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into new/feat/messages
- Loading branch information
Showing
86 changed files
with
5,312 additions
and
811 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
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({ | ||
path: 'messages', | ||
select: '-_id -__v -user', | ||
}) | ||
.select('-_id -__v -user') | ||
.lean(); | ||
|
||
if (!share || !share.conversationId || !share.isPublic) { | ||
return null; | ||
} | ||
|
||
return share; | ||
} 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) | ||
.select('-_id -__v -user') | ||
.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 }).select('-_id -__v -user').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 }).select('-_id -__v -user').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 }); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { createContext, useContext } from 'react'; | ||
type TShareContext = { isSharedConvo?: boolean }; | ||
|
||
export const ShareContext = createContext<TShareContext>({} as TShareContext); | ||
export const useShareContext = () => useContext(ShareContext); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { useState } from 'react'; | ||
import { Upload } from 'lucide-react'; | ||
import { useRecoilValue } from 'recoil'; | ||
import { useLocation } from 'react-router-dom'; | ||
import type { TConversation } from 'librechat-data-provider'; | ||
import DropDownMenu from '../Conversations/DropDownMenu'; | ||
import ShareButton from '../Conversations/ShareButton'; | ||
import HoverToggle from '../Conversations/HoverToggle'; | ||
import ExportButton from './ExportButton'; | ||
import store from '~/store'; | ||
|
||
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 ( | ||
<HoverToggle | ||
isActiveConvo={!!isActiveConvo} | ||
isPopoverActive={isPopoverActive} | ||
setIsPopoverActive={setIsPopoverActive} | ||
> | ||
<DropDownMenu | ||
icon={<Upload />} | ||
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 && ( | ||
<> | ||
<ExportButton conversation={conversation} setPopoverActive={setIsPopoverActive} /> | ||
<ShareButton | ||
conversationId={conversation.conversationId} | ||
title={conversation.title ?? ''} | ||
appendLabel={true} | ||
className="mb-[3.5px]" | ||
setPopoverActive={setIsPopoverActive} | ||
/> | ||
</> | ||
)} | ||
</DropDownMenu> | ||
</HoverToggle> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.