LibreChat/client/src/store/misc.ts
Samuel Path 304bba853c
💻 feat: Deeper MCP UI integration in the Chat UI (#9669)
* 💻 feat: deeper MCP UI integration in the chat UI using plugins

---------

Co-authored-by: Samuel Path <samuel.path@shopify.com>
Co-authored-by: Pierre-Luc Godin <pierreluc.godin@shopify.com>

* 💻 refactor: Migrate MCP UI resources from index-based to ID-based referencing

- Replace index-based resource markers with stable resource IDs
- Update plugin to parse \ui{resourceId} format instead of \ui0
- Refactor components to use useMessagesOperations instead of useSubmitMessage
- Add ShareMessagesProvider for UI resources in share view
- Add useConversationUIResources hook for cross-turn resource lookups
- Update parsers to generate resource IDs from content hashes
- Update all tests to use resource IDs instead of indices
- Add sandbox permissions for iframe popups
- Remove deprecated MCP tool context instructions

---------

Co-authored-by: Pierre-Luc Godin <pierreluc.godin@shopify.com>
2025-12-11 16:41:11 -05:00

74 lines
1.9 KiB
TypeScript

import { atom, selectorFamily } from 'recoil';
import { TAttachment } from 'librechat-data-provider';
import { atomWithLocalStorage } from './utils';
import { BadgeItem } from '~/common';
const hideBannerHint = atomWithLocalStorage('hideBannerHint', [] as string[]);
const messageAttachmentsMap = atom<Record<string, TAttachment[] | undefined>>({
key: 'messageAttachmentsMap',
default: {},
});
/**
* Selector to get attachments for a specific conversation.
*/
const conversationAttachmentsSelector = selectorFamily<
Record<string, TAttachment[]>,
string | undefined
>({
key: 'conversationAttachments',
get:
(conversationId) =>
({ get }) => {
if (!conversationId) {
return {};
}
const attachmentsMap = get(messageAttachmentsMap);
const result: Record<string, TAttachment[]> = {};
// Filter to only include attachments for this conversation
Object.entries(attachmentsMap).forEach(([messageId, attachments]) => {
if (!attachments) {
return;
}
const relevantAttachments = attachments.filter(
(attachment) => attachment.conversationId === conversationId,
);
if (relevantAttachments.length > 0) {
result[messageId] = relevantAttachments;
}
});
return result;
},
});
const queriesEnabled = atom<boolean>({
key: 'queriesEnabled',
default: true,
});
const isEditingBadges = atom<boolean>({
key: 'isEditingBadges',
default: false,
});
const chatBadges = atomWithLocalStorage<Pick<BadgeItem, 'id'>[]>('chatBadges', [
// When adding new badges, make sure to add them to useChatBadges.ts as well and add them as last item
// DO NOT CHANGE THE ORDER OF THE BADGES ALREADY IN THE ARRAY
{ id: '1' },
// { id: '2' },
]);
export default {
hideBannerHint,
messageAttachmentsMap,
conversationAttachmentsSelector,
queriesEnabled,
isEditingBadges,
chatBadges,
};