mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-24 04:10:15 +01:00
🔖 feat: Conversation Bookmarks (#3344)
* feat: add tags property in Conversation model * feat: add ConversationTag model * feat: add the tags parameter to getConvosByPage * feat: add API route to ConversationTag * feat: add types of ConversationTag * feat: add data access functions for conversation tags * feat: add Bookmark table component * feat: Add an action to bookmark * feat: add Bookmark nav component * fix: failed test * refactor: made 'Saved' tag a constant * feat: add new bookmark to current conversation * chore: Add comment * fix: delete tag from conversations when it's deleted * fix: Update the query cache when the tag title is changed. * chore: fix typo * refactor: add description of rebuilding bookmarks * chore: remove unused variables * fix: position when adding a new bookmark * refactor: add comment, rename a function * refactor: add a unique constraint in ConversationTag * chore: add localizations
This commit is contained in:
parent
d4d56281e3
commit
e565e0faab
65 changed files with 3751 additions and 36 deletions
|
|
@ -32,8 +32,10 @@ export const abortRequest = (endpoint: string) => `/api/ask/${endpoint}/abort`;
|
|||
|
||||
export const conversationsRoot = '/api/convos';
|
||||
|
||||
export const conversations = (pageNumber: string, isArchived?: boolean) =>
|
||||
`${conversationsRoot}?pageNumber=${pageNumber}${isArchived ? '&isArchived=true' : ''}`;
|
||||
export const conversations = (pageNumber: string, isArchived?: boolean, tags?: string[]) =>
|
||||
`${conversationsRoot}?pageNumber=${pageNumber}${isArchived ? '&isArchived=true' : ''}${tags
|
||||
?.map((tag) => `&tags=${tag}`)
|
||||
.join('')}`;
|
||||
|
||||
export const conversationById = (id: string) => `${conversationsRoot}/${id}`;
|
||||
|
||||
|
|
@ -188,3 +190,14 @@ export const roles = () => '/api/roles';
|
|||
export const getRole = (roleName: string) => `${roles()}/${roleName.toLowerCase()}`;
|
||||
export const updatePromptPermissions = (roleName: string) =>
|
||||
`${roles()}/${roleName.toLowerCase()}/prompts`;
|
||||
|
||||
/* Conversation Tags */
|
||||
export const conversationTags = (tag?: string) => `/api/tags${tag ? `/${tag}` : ''}`;
|
||||
|
||||
export const conversationTagsList = (pageNumber: string, sort?: string, order?: string) =>
|
||||
`${conversationTags()}/list?pageNumber=${pageNumber}${sort ? `&sort=${sort}` : ''}${
|
||||
order ? `&order=${order}` : ''
|
||||
}`;
|
||||
|
||||
export const addTagToConversation = (conversationId: string) =>
|
||||
`${conversationsRoot}/tags/${conversationId}`;
|
||||
|
|
|
|||
|
|
@ -424,7 +424,8 @@ export const listConversations = (
|
|||
// Assuming params has a pageNumber property
|
||||
const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided
|
||||
const isArchived = params?.isArchived || false; // Default to false if not provided
|
||||
return request.get(endpoints.conversations(pageNumber, isArchived));
|
||||
const tags = params?.tags || []; // Default to an empty array if not provided
|
||||
return request.get(endpoints.conversations(pageNumber, isArchived, tags));
|
||||
};
|
||||
|
||||
export const listConversationsByQuery = (
|
||||
|
|
@ -541,3 +542,34 @@ export function updatePromptPermissions(
|
|||
): Promise<m.UpdatePromptPermResponse> {
|
||||
return request.put(endpoints.updatePromptPermissions(variables.roleName), variables.updates);
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
export function getConversationTags(): Promise<t.TConversationTagsResponse> {
|
||||
return request.get(endpoints.conversationTags());
|
||||
}
|
||||
|
||||
export function createConversationTag(
|
||||
payload: t.TConversationTagRequest,
|
||||
): Promise<t.TConversationTagResponse> {
|
||||
return request.post(endpoints.conversationTags(), payload);
|
||||
}
|
||||
|
||||
export function updateConversationTag(
|
||||
tag: string,
|
||||
payload: t.TConversationTagRequest,
|
||||
): Promise<t.TConversationTagResponse> {
|
||||
return request.put(endpoints.conversationTags(tag), payload);
|
||||
}
|
||||
export function deleteConversationTag(tag: string): Promise<t.TConversationTagResponse> {
|
||||
return request.delete(endpoints.conversationTags(tag));
|
||||
}
|
||||
|
||||
export function addTagToConversation(
|
||||
conversationId: string,
|
||||
payload: t.TTagConversationRequest,
|
||||
): Promise<t.TTagConversationResponse> {
|
||||
return request.put(endpoints.addTagToConversation(conversationId), payload);
|
||||
}
|
||||
export function rebuildConversationTags(): Promise<t.TConversationTagsResponse> {
|
||||
return request.post(endpoints.conversationTags('rebuild'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export enum QueryKeys {
|
|||
categories = 'categories',
|
||||
randomPrompts = 'randomPrompts',
|
||||
roles = 'roles',
|
||||
conversationTags = 'conversationTags',
|
||||
}
|
||||
|
||||
export enum MutationKeys {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,21 @@ export const useGetSharedMessages = (
|
|||
);
|
||||
};
|
||||
|
||||
export const useGetConversationTags = (
|
||||
config?: UseQueryOptions<t.TConversationTagsResponse>,
|
||||
): QueryObserverResult<t.TConversationTagsResponse> => {
|
||||
return useQuery<t.TConversationTagsResponse>(
|
||||
[QueryKeys.conversationTags],
|
||||
() => dataService.getConversationTags(),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useGetUserBalance = (
|
||||
config?: UseQueryOptions<string>,
|
||||
): QueryObserverResult<string> => {
|
||||
|
|
|
|||
|
|
@ -371,6 +371,7 @@ export const tConversationSchema = z.object({
|
|||
updatedAt: z.string(),
|
||||
modelLabel: z.string().nullable().optional(),
|
||||
examples: z.array(tExampleSchema).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
/* Prefer modelLabel over chatGptLabel */
|
||||
chatGptLabel: z.string().nullable().optional(),
|
||||
userLabel: z.string().optional(),
|
||||
|
|
@ -476,6 +477,17 @@ export const tSharedLinkSchema = z.object({
|
|||
});
|
||||
export type TSharedLink = z.infer<typeof tSharedLinkSchema>;
|
||||
|
||||
export const tConversationTagSchema = z.object({
|
||||
user: z.string(),
|
||||
tag: z.string(),
|
||||
description: z.string().optional(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
count: z.number(),
|
||||
position: z.number(),
|
||||
});
|
||||
export type TConversationTag = z.infer<typeof tConversationTagSchema>;
|
||||
|
||||
export const openAISchema = tConversationSchema
|
||||
.pick({
|
||||
model: true,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type {
|
|||
TSharedLink,
|
||||
TConversation,
|
||||
EModelEndpoint,
|
||||
TConversationTag,
|
||||
} from './schemas';
|
||||
import type { TSpecsConfig } from './models';
|
||||
export type TOpenAIMessage = OpenAI.Chat.ChatCompletionMessageParam;
|
||||
|
|
@ -170,6 +171,25 @@ export type TSharedLinkResponse = TSharedLink;
|
|||
export type TSharedLinksResponse = TSharedLink[];
|
||||
export type TDeleteSharedLinkResponse = TSharedLink;
|
||||
|
||||
// type for getting conversation tags
|
||||
export type TConversationTagsResponse = TConversationTag[];
|
||||
// type for creating conversation tag
|
||||
export type TConversationTagRequest = Partial<
|
||||
Omit<TConversationTag, 'createdAt' | 'updatedAt' | 'count' | 'user'>
|
||||
> & {
|
||||
conversationId?: string;
|
||||
addToConversation?: boolean;
|
||||
};
|
||||
|
||||
export type TConversationTagResponse = TConversationTag;
|
||||
|
||||
// type for tagging conversation
|
||||
export type TTagConversationRequest = {
|
||||
conversationId: string;
|
||||
tags: string[];
|
||||
};
|
||||
export type TTagConversationResponse = string[];
|
||||
|
||||
export type TForkConvoRequest = {
|
||||
messageId: string;
|
||||
conversationId: string;
|
||||
|
|
|
|||
|
|
@ -173,3 +173,9 @@ export type UpdatePromptPermOptions = MutationOptions<
|
|||
unknown,
|
||||
types.TError
|
||||
>;
|
||||
|
||||
export type UpdateConversationTagOptions = MutationOptions<
|
||||
types.TConversationTag,
|
||||
types.TConversationTagRequest
|
||||
>;
|
||||
export type DeleteConversationTagOptions = MutationOptions<types.TConversationTag, string>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { InfiniteData } from '@tanstack/react-query';
|
||||
import type { TMessage, TConversation, TSharedLink } from '../schemas';
|
||||
import type * as t from '../types';
|
||||
import type { TMessage, TConversation, TSharedLink, TConversationTag } from '../schemas';
|
||||
|
||||
export type Conversation = {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
|
|
@ -18,6 +19,7 @@ export type ConversationListParams = {
|
|||
pageNumber: string; // Add this line
|
||||
conversationId?: string;
|
||||
isArchived?: boolean;
|
||||
tags?: string[];
|
||||
};
|
||||
|
||||
// Type for the response from the conversation list API
|
||||
|
|
@ -68,3 +70,5 @@ export type AllPromptGroupsFilterRequest = {
|
|||
};
|
||||
|
||||
export type AllPromptGroupsResponse = t.TPromptGroup[];
|
||||
|
||||
export type ConversationTagsResponse = TConversationTag[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue