mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-25 11:46:12 +01:00
🚀 feat: Shared Links (#2772)
* ✨ feat(types): add necessary types for shared link feature * ✨ feat: add shared links functions to data service Added functions for retrieving, creating, updating, and deleting shared links and shared messages. * ✨ feat: Add useGetSharedMessages hook to fetch shared messages by shareId Adds a new hook `useGetSharedMessages` which fetches shared messages based on the provided shareId. * ✨ feat: Add share schema and data access functions to API models * ✨ feat: Add share endpoint to API The GET /api/share/${shareId} is exposed to the public, so authentication is not required. Other paths require authentication. * ♻️ refactor(utils): generalize react-query cache manipulation functions Introduces generic functions for manipulating react-query cache entries, marking a refinement in how query cache data is managed. It aims to enhance the flexibility and reusability of the cache interaction patterns within our application. - Replaced specific index names with more generic terms in queries.ts, enhancing consistency across data handling functions. - Introduced new utility functions in collection.ts for adding, updating, and deleting data entries in an InfiniteData<TCollection>. These utility functions (`addData`, `updateData`, `deleteData`, `findPage`) are designed to be re-usable across different data types and collections. - Adapted existing conversation utility functions in convos.ts to leverage these new generic utilities. * ✨ feat(shared-link): add functions to manipulate shared link cache list implemented new utility functions to handle additions, updates, and deletions in the shared link cache list. * ✨ feat: Add mutations and queries for shared links * ✨ feat(shared-link): add `Share` button to conversation list - Added a share button in each conversation in the conversation list. - Implemented functionality where clicking the share button triggers a POST request to the API. - The API checks if a share link was already created for the conversation today; if so, it returns the existing link. - If no link was created for today, the API will create a new share link and return it. - Each click on the share button results in a new API request, following the specification similar to ChatGPT's share link feature. * ♻️ refactor(hooks): generalize useNavScrolling for broader use - Modified `useNavScrolling` to accept a generic type parameter `TData`, allowing it to be used with different data structures besides `ConversationListResponse`. - Updated instances in `Nav.tsx` and `ArchivedChatsTable.tsx` to explicitly specify `ConversationListResponse` as the type argument when invoking `useNavScrolling`. * ✨ feat(settings): add shared links listing table with delete functionality in settings - Integrated a delete button for each shared link in the table, allowing users to remove links as needed. * ♻️ refactor(components): separate `EndpointIcon` from `Icon` component for standalone use * ♻️ refactor: update useGetSharedMessages to return TSharedLink - Modified the useGetSharedMessages hook to return not only a list of TMessage but also the TSharedLink itself. - This change was necessary to support displaying the title and date in the Shared Message UI, which requires data from TSharedLink. * ✨ feat(shared link): add UI for displaying shared conversations without authentication - Implemented a new UI component to display shared conversations, designed to be accessible without requiring authentication. - Reused components from the authenticated Messages module where possible. Copied and adapted components that could not be directly reused to fit the non-authenticated context. * 🔧 chore: Add translations Translate labels only. Messages remain in English as they are possibly subject to change. * ♻️ refactor: add icon and tooltip props to EditMenuButton component * moved icon and popover to arguments so that EditMenuButton can be reused. * modified so that when a ShareButton is closed, the parent DropdownMenu is also closed. * ♻️irefactor: added DropdownMenu for Export and Share * ♻️ refactor: renamed component names more intuitive * More accurate naming of the dropdown menu. * When the export button is closed, the parent dropdown menu is also closed. * 🌍 chore: updated translations * 🐞 Fix: OpenID Profile Image Download (#2757) * Add fetch requirement Fixes - error: [openidStrategy] downloadImage: Error downloading image at URL "https://graph.microsoft.com/v1.0/me/photo/$value": TypeError: response.buffer is not a function * Update openidStrategy.js --------- Co-authored-by: Danny Avila <danacordially@gmail.com> * 🚑 fix(export): Issue exporting Conversation with Assistants (#2769) * 🚑 fix(export): use content as text if content is present in the message If the endpoint is assistants, the text of the message goes into content, not message.text. * refactor(ExportModel): TypeScript, remove unused code --------- Co-authored-by: Yuichi Ohneda <ohneda@gmail.com> * 📤style: export button icon (#2752) * refactor(ShareDialog): logic and styling * refactor(ExportAndShareMenu): imports order and icon update * chore: imports * chore: imports/render logic * feat: message branching * refactor: add optional config to useGetStartupConfig * refactor: disable endpoints query * chore: fix search view styling gradient in light mode * style: ShareView gradient styling * refactor(Share): use select queries * style: shared link table buttons * localization and dark text styling * style: fix clipboard button layout shift app-wide and add localization for copy code * support assistants message content in shared links, add useCopyToClipboard, add copy buttons to Search Messages and Shared Link Messages * add localizations * comparisons --------- Co-authored-by: Yuichi Ohneda <ohneda@gmail.com> Co-authored-by: bsu3338 <bsu3338@users.noreply.github.com> Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>
This commit is contained in:
parent
38ad36c1c5
commit
f0e8cca5df
78 changed files with 4683 additions and 317 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,34 @@ export function getMessagesByConvoId(conversationId: string): Promise<s.TMessage
|
|||
return request.get(endpoints.messages(conversationId));
|
||||
}
|
||||
|
||||
export function getSharedMessages(shareId: string): Promise<t.TSharedMessagesResponse> {
|
||||
return request.get(endpoints.shareMessages(shareId));
|
||||
}
|
||||
|
||||
export const listSharedLinks = (
|
||||
params?: q.SharedLinkListParams,
|
||||
): Promise<q.SharedLinksResponse> => {
|
||||
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<t.TSharedLinkResponse> {
|
||||
return request.get(endpoints.shareMessages(shareId));
|
||||
}
|
||||
|
||||
export function createSharedLink(payload: t.TSharedLinkRequest): Promise<t.TSharedLinkResponse> {
|
||||
return request.post(endpoints.createSharedLink, payload);
|
||||
}
|
||||
|
||||
export function updateSharedLink(payload: t.TSharedLinkRequest): Promise<t.TSharedLinkResponse> {
|
||||
return request.patch(endpoints.updateSharedLink, payload);
|
||||
}
|
||||
|
||||
export function deleteSharedLink(shareId: string): Promise<t.TDeleteSharedLinkResponse> {
|
||||
return request.delete(endpoints.shareMessages(shareId));
|
||||
}
|
||||
|
||||
export function updateMessage(payload: t.TUpdateMessageRequest): Promise<unknown> {
|
||||
const { conversationId, messageId, text } = payload;
|
||||
if (!conversationId) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export enum QueryKeys {
|
||||
messages = 'messages',
|
||||
sharedMessages = 'sharedMessages',
|
||||
sharedLinks = 'sharedLinks',
|
||||
allConversations = 'allConversations',
|
||||
archivedConversations = 'archivedConversations',
|
||||
searchConversations = 'searchConversations',
|
||||
|
|
|
|||
|
|
@ -60,6 +60,22 @@ export const useGetMessagesByConvoId = <TData = s.TMessage[]>(
|
|||
);
|
||||
};
|
||||
|
||||
export const useGetSharedMessages = (
|
||||
shareId: string,
|
||||
config?: UseQueryOptions<t.TSharedMessagesResponse>,
|
||||
): QueryObserverResult<t.TSharedMessagesResponse> => {
|
||||
return useQuery<t.TSharedMessagesResponse>(
|
||||
[QueryKeys.sharedMessages, shareId],
|
||||
() => dataService.getSharedMessages(shareId),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useGetUserBalance = (
|
||||
config?: UseQueryOptions<string>,
|
||||
): QueryObserverResult<string> => {
|
||||
|
|
@ -398,7 +414,9 @@ export const useUpdateUserPluginsMutation = (): UseMutationResult<
|
|||
});
|
||||
};
|
||||
|
||||
export const useGetStartupConfig = (): QueryObserverResult<t.TStartupConfig> => {
|
||||
export const useGetStartupConfig = (
|
||||
config?: UseQueryOptions<t.TStartupConfig>,
|
||||
): QueryObserverResult<t.TStartupConfig> => {
|
||||
return useQuery<t.TStartupConfig>(
|
||||
[QueryKeys.startupConfig],
|
||||
() => dataService.getStartupConfig(),
|
||||
|
|
@ -406,6 +424,7 @@ export const useGetStartupConfig = (): QueryObserverResult<t.TStartupConfig> =>
|
|||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -381,6 +381,19 @@ export type TConversation = z.infer<typeof tConversationSchema> & {
|
|||
presetOverride?: Partial<TPreset>;
|
||||
};
|
||||
|
||||
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<typeof tSharedLinkSchema>;
|
||||
|
||||
export const openAISchema = tConversationSchema
|
||||
.pick({
|
||||
model: true,
|
||||
|
|
|
|||
|
|
@ -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<TSharedLink, 'messages'> & {
|
||||
messages: TMessage[];
|
||||
};
|
||||
export type TSharedLinkRequest = Partial<
|
||||
Omit<TSharedLink, 'messages' | 'createdAt' | 'updatedAt'>
|
||||
> & {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
export type TSharedLinkResponse = TSharedLink;
|
||||
export type TSharedLinksResponse = TSharedLink[];
|
||||
export type TDeleteSharedLinkResponse = TSharedLink;
|
||||
|
||||
export type TForkConvoRequest = {
|
||||
messageId: string;
|
||||
conversationId: string;
|
||||
|
|
|
|||
|
|
@ -90,3 +90,13 @@ export type DeleteConversationOptions = MutationOptions<
|
|||
>;
|
||||
|
||||
export type ForkConvoOptions = MutationOptions<types.TForkConvoResponse, types.TForkConvoRequest>;
|
||||
|
||||
export type CreateSharedLinkOptions = MutationOptions<
|
||||
types.TSharedLink,
|
||||
Partial<types.TSharedLink>
|
||||
>;
|
||||
export type UpdateSharedLinkOptions = MutationOptions<
|
||||
types.TSharedLink,
|
||||
Partial<types.TSharedLink>
|
||||
>;
|
||||
export type DeleteSharedLinkOptions = MutationOptions<types.TSharedLink, { shareId: string }>;
|
||||
|
|
|
|||
|
|
@ -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<TSharedLink, 'messages'> & {
|
||||
messages: TMessage[];
|
||||
};
|
||||
export type SharedLinkListParams = Omit<ConversationListParams, 'isArchived' | 'conversationId'> & {
|
||||
isPublic?: boolean;
|
||||
};
|
||||
|
||||
export type SharedLinksResponse = Omit<ConversationListResponse, 'conversations' | 'messages'> & {
|
||||
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<SharedLinkListResponse>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue