mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-21 10:50:14 +01:00
🔗 fix: Add branch-specific shared links (targetMessageId) (#10016)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
* feat: Enhance shared link functionality with target message support * refactor: Remove comment on compound index in share schema * chore: Reorganize imports in ShareButton component for clarity * refactor: Integrate Recoil for latest message tracking in ShareButton component --------- Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
ded3f2e998
commit
5566cc499e
8 changed files with 129 additions and 12 deletions
|
|
@ -82,6 +82,77 @@ function anonymizeMessages(messages: t.IMessage[], newConvoId: string): t.IMessa
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter messages up to and including the target message (branch-specific)
|
||||
* Similar to getMessagesUpToTargetLevel from fork utilities
|
||||
*/
|
||||
function getMessagesUpToTarget(messages: t.IMessage[], targetMessageId: string): t.IMessage[] {
|
||||
if (!messages || messages.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// If only one message and it's the target, return it
|
||||
if (messages.length === 1 && messages[0]?.messageId === targetMessageId) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
// Create a map of parentMessageId to children messages
|
||||
const parentToChildrenMap = new Map<string, t.IMessage[]>();
|
||||
for (const message of messages) {
|
||||
const parentId = message.parentMessageId || Constants.NO_PARENT;
|
||||
if (!parentToChildrenMap.has(parentId)) {
|
||||
parentToChildrenMap.set(parentId, []);
|
||||
}
|
||||
parentToChildrenMap.get(parentId)?.push(message);
|
||||
}
|
||||
|
||||
// Find the target message
|
||||
const targetMessage = messages.find((msg) => msg.messageId === targetMessageId);
|
||||
if (!targetMessage) {
|
||||
// If target not found, return all messages for backwards compatibility
|
||||
return messages;
|
||||
}
|
||||
|
||||
const visited = new Set<string>();
|
||||
const rootMessages = parentToChildrenMap.get(Constants.NO_PARENT) || [];
|
||||
let currentLevel = rootMessages.length > 0 ? [...rootMessages] : [targetMessage];
|
||||
const results = new Set<t.IMessage>(currentLevel);
|
||||
|
||||
// Check if the target message is at the root level
|
||||
if (
|
||||
currentLevel.some((msg) => msg.messageId === targetMessageId) &&
|
||||
targetMessage.parentMessageId === Constants.NO_PARENT
|
||||
) {
|
||||
return Array.from(results);
|
||||
}
|
||||
|
||||
// Iterate level by level until the target is found
|
||||
let targetFound = false;
|
||||
while (!targetFound && currentLevel.length > 0) {
|
||||
const nextLevel: t.IMessage[] = [];
|
||||
for (const node of currentLevel) {
|
||||
if (visited.has(node.messageId)) {
|
||||
continue;
|
||||
}
|
||||
visited.add(node.messageId);
|
||||
const children = parentToChildrenMap.get(node.messageId) || [];
|
||||
for (const child of children) {
|
||||
if (visited.has(child.messageId)) {
|
||||
continue;
|
||||
}
|
||||
nextLevel.push(child);
|
||||
results.add(child);
|
||||
if (child.messageId === targetMessageId) {
|
||||
targetFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentLevel = nextLevel;
|
||||
}
|
||||
|
||||
return Array.from(results);
|
||||
}
|
||||
|
||||
/** Factory function that takes mongoose instance and returns the methods */
|
||||
export function createShareMethods(mongoose: typeof import('mongoose')) {
|
||||
/**
|
||||
|
|
@ -102,6 +173,12 @@ export function createShareMethods(mongoose: typeof import('mongoose')) {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Filter messages based on targetMessageId if present (branch-specific sharing)
|
||||
let messagesToShare = share.messages;
|
||||
if (share.targetMessageId) {
|
||||
messagesToShare = getMessagesUpToTarget(share.messages, share.targetMessageId);
|
||||
}
|
||||
|
||||
const newConvoId = anonymizeConvoId(share.conversationId);
|
||||
const result: t.SharedMessagesResult = {
|
||||
shareId: share.shareId || shareId,
|
||||
|
|
@ -110,7 +187,7 @@ export function createShareMethods(mongoose: typeof import('mongoose')) {
|
|||
createdAt: share.createdAt,
|
||||
updatedAt: share.updatedAt,
|
||||
conversationId: newConvoId,
|
||||
messages: anonymizeMessages(share.messages, newConvoId),
|
||||
messages: anonymizeMessages(messagesToShare, newConvoId),
|
||||
};
|
||||
|
||||
return result;
|
||||
|
|
@ -239,6 +316,7 @@ export function createShareMethods(mongoose: typeof import('mongoose')) {
|
|||
async function createSharedLink(
|
||||
user: string,
|
||||
conversationId: string,
|
||||
targetMessageId?: string,
|
||||
): Promise<t.CreateShareResult> {
|
||||
if (!user || !conversationId) {
|
||||
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
||||
|
|
@ -249,7 +327,12 @@ export function createShareMethods(mongoose: typeof import('mongoose')) {
|
|||
const Conversation = mongoose.models.Conversation as SchemaWithMeiliMethods;
|
||||
|
||||
const [existingShare, conversationMessages] = await Promise.all([
|
||||
SharedLink.findOne({ conversationId, user, isPublic: true })
|
||||
SharedLink.findOne({
|
||||
conversationId,
|
||||
user,
|
||||
isPublic: true,
|
||||
...(targetMessageId && { targetMessageId }),
|
||||
})
|
||||
.select('-_id -__v -user')
|
||||
.lean() as Promise<t.ISharedLink | null>,
|
||||
Message.find({ conversationId, user }).sort({ createdAt: 1 }).lean(),
|
||||
|
|
@ -259,10 +342,15 @@ export function createShareMethods(mongoose: typeof import('mongoose')) {
|
|||
logger.error('[createSharedLink] Share already exists', {
|
||||
user,
|
||||
conversationId,
|
||||
targetMessageId,
|
||||
});
|
||||
throw new ShareServiceError('Share already exists', 'SHARE_EXISTS');
|
||||
} else if (existingShare) {
|
||||
await SharedLink.deleteOne({ conversationId, user });
|
||||
await SharedLink.deleteOne({
|
||||
conversationId,
|
||||
user,
|
||||
...(targetMessageId && { targetMessageId }),
|
||||
});
|
||||
}
|
||||
|
||||
const conversation = (await Conversation.findOne({ conversationId, user }).lean()) as {
|
||||
|
|
@ -291,6 +379,7 @@ export function createShareMethods(mongoose: typeof import('mongoose')) {
|
|||
messages: conversationMessages,
|
||||
title,
|
||||
user,
|
||||
...(targetMessageId && { targetMessageId }),
|
||||
});
|
||||
|
||||
return { shareId, conversationId };
|
||||
|
|
@ -302,6 +391,7 @@ export function createShareMethods(mongoose: typeof import('mongoose')) {
|
|||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
user,
|
||||
conversationId,
|
||||
targetMessageId,
|
||||
});
|
||||
throw new ShareServiceError('Error creating shared link', 'SHARE_CREATE_ERROR');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue