diff --git a/web/src/api/asset_interfaces/chat/chatInterfaces.ts b/web/src/api/asset_interfaces/chat/chatInterfaces.ts index e8ee8fdd8..85c5e9557 100644 --- a/web/src/api/asset_interfaces/chat/chatInterfaces.ts +++ b/web/src/api/asset_interfaces/chat/chatInterfaces.ts @@ -12,6 +12,7 @@ export interface BusterChat { created_by_id: string; created_by_name: string; created_by_avatar: string | null; + feedback: 'negative' | null; // pinned_message_id: string | null; MAYBE WE NEED THIS? } diff --git a/web/src/api/buster_rest/chats/queryRequests.ts b/web/src/api/buster_rest/chats/queryRequests.ts index f27d305e6..1a65de75f 100644 --- a/web/src/api/buster_rest/chats/queryRequests.ts +++ b/web/src/api/buster_rest/chats/queryRequests.ts @@ -7,7 +7,8 @@ import { getChat_server, updateChat, deleteChat, - getListLogs + getListLogs, + duplicateChat } from './requests'; import type { IBusterChat, IBusterChatMessage } from '@/api/asset_interfaces/chat'; import { queryKeys } from '@/api/query_keys'; @@ -115,10 +116,22 @@ export const prefetchGetChat = async ( }; export const useUpdateChat = () => { + const queryClient = useQueryClient(); return useMutation({ mutationFn: updateChat, - onMutate: () => { + onMutate: (data) => { //this is actually handled in @useChatUpdate file + + //except for the chat title and feedback + if (data.title || data.feedback !== undefined) { + const options = queryKeys.chatsGetChat(data.id); + queryClient.setQueryData(options.queryKey, (old) => { + return { + ...old!, + ...data + }; + }); + } } }); }; @@ -158,3 +171,9 @@ export const useGetChatMessage = ( }); return data; }; + +export const useDuplicateChat = () => { + return useMutation({ + mutationFn: duplicateChat + }); +}; diff --git a/web/src/api/buster_rest/chats/requests.ts b/web/src/api/buster_rest/chats/requests.ts index 30bb37a24..f52971894 100644 --- a/web/src/api/buster_rest/chats/requests.ts +++ b/web/src/api/buster_rest/chats/requests.ts @@ -2,6 +2,7 @@ import { mainApi } from '../instances'; import { serverFetch } from '../../createServerInstance'; import type { BusterChatListItem, BusterChat } from '@/api/asset_interfaces/chat'; import type { + DuplicateChatParams, GetChatListParams, GetChatParams, UpdateChatParams @@ -55,3 +56,13 @@ export const updateChat = async ({ id, ...data }: UpdateChatParams): Promise => { return mainApi.delete(`${CHATS_BASE}`, { data: { ids } }).then((res) => res.data); }; + +export const duplicateChat = async ({ + id, + message_id, + share_with_same_people +}: DuplicateChatParams): Promise => { + return mainApi + .post(`${CHATS_BASE}/duplicate`, { id, message_id, share_with_same_people }) + .then((res) => res.data); +}; diff --git a/web/src/api/buster_rest/metrics/interfaces.ts b/web/src/api/buster_rest/metrics/interfaces.ts index 93c158426..c5a247a8b 100644 --- a/web/src/api/buster_rest/metrics/interfaces.ts +++ b/web/src/api/buster_rest/metrics/interfaces.ts @@ -37,8 +37,6 @@ export type UpdateMetricParams = { chart_config?: BusterChartConfigProps; /** Flag to save the current draft state */ save_draft?: boolean; - /** Feedback status for the metric */ - feedback?: 'negative'; /** Admin only: verification status update */ status?: VerificationStatus; /** file in yaml format to update */ diff --git a/web/src/api/buster_rest/metrics/queryRequests.ts b/web/src/api/buster_rest/metrics/queryRequests.ts index 2e535fb5f..dda8b907e 100644 --- a/web/src/api/buster_rest/metrics/queryRequests.ts +++ b/web/src/api/buster_rest/metrics/queryRequests.ts @@ -3,6 +3,7 @@ import { QueryClient } from '@tanstack/react-query'; import { useMemoizedFn, useDebounceFn } from '@/hooks'; import { deleteMetrics, + duplicateMetric, getMetric, getMetric_server, getMetricData, @@ -382,3 +383,9 @@ export const useRemoveMetricFromDashboard = () => { mutationFn: removeMetricFromDashboard }); }; + +export const useDuplicateMetric = () => { + return useMutation({ + mutationFn: duplicateMetric + }); +}; diff --git a/web/src/api/request_interfaces/chats/interfaces.ts b/web/src/api/request_interfaces/chats/interfaces.ts index c424cbac3..a59b181af 100644 --- a/web/src/api/request_interfaces/chats/interfaces.ts +++ b/web/src/api/request_interfaces/chats/interfaces.ts @@ -51,6 +51,8 @@ export interface UpdateChatParams { title?: string; /** Optional flag to set the chat's favorite status */ is_favorited?: boolean; + /** Optional feedback to set for the chat */ + feedback?: 'negative' | null; } export interface ChatsSearchParams { @@ -63,6 +65,13 @@ export interface DuplicateChatParams { id: string; /** The message ID to start the duplication from */ message_id: string; - /** The target chat ID to duplicate content to */ - chat_id: string; + /** Whether to share the duplicated chat with the same people as the source chat */ + share_with_same_people: boolean; +} + +export interface DuplicateChatResponse { + /** The unique identifier of the duplicated chat */ + id: string; + /** The title of the duplicated chat */ + title: string; } diff --git a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatContent.tsx b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatContent.tsx index 131e8a6d2..05667081d 100644 --- a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatContent.tsx +++ b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatContent.tsx @@ -8,6 +8,7 @@ import { ChatInput } from './ChatInput'; const autoClass = 'mx-auto max-w-[600px] w-full'; export const ChatContent: React.FC<{}> = React.memo(({}) => { + const chatId = useChatIndividualContextSelector((state) => state.chatId); const chatMessageIds = useChatIndividualContextSelector((state) => state.chatMessageIds); return ( @@ -16,7 +17,7 @@ export const ChatContent: React.FC<{}> = React.memo(({}) => {
{chatMessageIds?.map((messageId) => (
- +
))}
diff --git a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageBlock.tsx b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageBlock.tsx index 20fae70f0..5ea829975 100644 --- a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageBlock.tsx +++ b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageBlock.tsx @@ -5,7 +5,8 @@ import { useGetChatMessage } from '@/api/buster_rest/chats'; export const ChatMessageBlock: React.FC<{ messageId: string; -}> = React.memo(({ messageId }) => { + chatId: string; +}> = React.memo(({ messageId, chatId }) => { const requestMessage = useGetChatMessage(messageId, (message) => message?.request_message); const isCompletedStream = useGetChatMessage(messageId, (x) => x?.isCompletedStream); @@ -14,7 +15,11 @@ export const ChatMessageBlock: React.FC<{ return (
- +
); }); diff --git a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageOptions.tsx b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageOptions.tsx new file mode 100644 index 000000000..b9bdb7142 --- /dev/null +++ b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageOptions.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Button } from '@/components/ui/buttons'; +import { AppTooltip } from '@/components/ui/tooltip'; +import { Copy, ThumbsDown } from '@/components/ui/icons'; +import { ThumbsDown as ThumbsDownFilled } from '@/components/ui/icons/NucleoIconFilled'; +import { useDuplicateChat, useGetChat, useUpdateChat } from '@/api/buster_rest/chats'; + +export const ChatMessageOptions: React.FC<{ + messageId: string; + chatId: string; +}> = React.memo(({ messageId, chatId }) => { + const { mutateAsync: duplicateChat, isPending: isCopying } = useDuplicateChat(); + const { mutateAsync: updateChat } = useUpdateChat(); + const { data: feedback } = useGetChat({ id: chatId }, (data) => data.feedback); + + return ( +
+ +
+ ); +}); + +ChatMessageOptions.displayName = 'ChatMessageOptions'; diff --git a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessages.tsx b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessages.tsx index 17c60de80..c632c56b5 100644 --- a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessages.tsx +++ b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessages.tsx @@ -3,14 +3,16 @@ import { MessageContainer } from '../MessageContainer'; import { ChatResponseMessageSelector } from './ChatResponseMessageSelector'; import { ChatResponseReasoning } from './ChatResponseReasoning'; import { useGetChatMessage } from '@/api/buster_rest/chats'; +import { ChatMessageOptions } from '../ChatMessageOptions'; interface ChatResponseMessagesProps { isCompletedStream: boolean; messageId: string; + chatId: string; } export const ChatResponseMessages: React.FC = React.memo( - ({ isCompletedStream, messageId }) => { + ({ chatId, isCompletedStream, messageId }) => { const responseMessageIds = useGetChatMessage(messageId, (x) => x?.response_message_ids || [])!; const lastReasoningMessageId = useGetChatMessage( messageId, @@ -36,6 +38,8 @@ export const ChatResponseMessages: React.FC = React.m /> ))} + + {isCompletedStream && } ); }