ctx_management: summarize on command button (#2479)
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { Message } from '../../types/message';
|
||||
import {
|
||||
manageContextFromBackend,
|
||||
convertApiMessageToFrontendMessage,
|
||||
createSummarizationRequestMessage,
|
||||
} from './index';
|
||||
|
||||
// Define the context management interface
|
||||
interface ChatContextManagerState {
|
||||
summaryContent: string;
|
||||
summarizedThread: Message[];
|
||||
isSummaryModalOpen: boolean;
|
||||
isLoadingSummary: boolean;
|
||||
errorLoadingSummary: boolean;
|
||||
preparingManualSummary: boolean;
|
||||
}
|
||||
|
||||
interface ChatContextManagerActions {
|
||||
updateSummary: (newSummaryContent: string) => void;
|
||||
resetMessagesWithSummary: (
|
||||
messages: Message[],
|
||||
setMessages: (messages: Message[]) => void,
|
||||
ancestorMessages: Message[],
|
||||
setAncestorMessages: (messages: Message[]) => void,
|
||||
summaryContent: string
|
||||
) => void;
|
||||
openSummaryModal: () => void;
|
||||
closeSummaryModal: () => void;
|
||||
hasContextHandlerContent: (message: Message) => boolean;
|
||||
hasContextLengthExceededContent: (message: Message) => boolean;
|
||||
hasSummarizationRequestedContent: (message: Message) => boolean;
|
||||
getContextHandlerType: (message: Message) => 'contextLengthExceeded' | 'summarizationRequested';
|
||||
handleContextLengthExceeded: (messages: Message[]) => Promise<void>;
|
||||
handleManualSummarization: (
|
||||
messages: Message[],
|
||||
setMessages: (messages: Message[]) => void
|
||||
) => void;
|
||||
}
|
||||
|
||||
// Create the context
|
||||
const ChatContextManagerContext = createContext<
|
||||
(ChatContextManagerState & ChatContextManagerActions) | undefined
|
||||
>(undefined);
|
||||
|
||||
// Create the provider component
|
||||
export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [summaryContent, setSummaryContent] = useState<string>('');
|
||||
const [summarizedThread, setSummarizedThread] = useState<Message[]>([]);
|
||||
const [isSummaryModalOpen, setIsSummaryModalOpen] = useState<boolean>(false);
|
||||
const [isLoadingSummary, setIsLoadingSummary] = useState<boolean>(false);
|
||||
const [errorLoadingSummary, setErrorLoadingSummary] = useState<boolean>(false);
|
||||
const [preparingManualSummary, setPreparingManualSummary] = useState<boolean>(false);
|
||||
|
||||
const handleContextLengthExceeded = async (messages: Message[]): Promise<void> => {
|
||||
setIsLoadingSummary(true);
|
||||
setErrorLoadingSummary(false);
|
||||
setPreparingManualSummary(true);
|
||||
|
||||
try {
|
||||
// 2. Now get the summary from the backend
|
||||
const summaryResponse = await manageContextFromBackend({
|
||||
messages: messages,
|
||||
manageAction: 'summarize',
|
||||
});
|
||||
|
||||
// Convert API messages to frontend messages
|
||||
const convertedMessages = summaryResponse.messages.map(
|
||||
(apiMessage) => convertApiMessageToFrontendMessage(apiMessage, false, true) // do not show to user but send to llm
|
||||
);
|
||||
|
||||
// Extract summary from the first message
|
||||
const summaryMessage = convertedMessages[0].content[0];
|
||||
if (summaryMessage.type === 'text') {
|
||||
const summary = summaryMessage.text;
|
||||
setSummaryContent(summary);
|
||||
setSummarizedThread(convertedMessages);
|
||||
}
|
||||
|
||||
setIsLoadingSummary(false);
|
||||
} catch (err) {
|
||||
console.error('Error handling context length exceeded:', err);
|
||||
setErrorLoadingSummary(true);
|
||||
setIsLoadingSummary(false);
|
||||
} finally {
|
||||
setPreparingManualSummary(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleManualSummarization = (
|
||||
messages: Message[],
|
||||
setMessages: (messages: Message[]) => void
|
||||
): void => {
|
||||
// add some messages to the message thread
|
||||
// these messages will be filtered out in chat view
|
||||
// but they will also be what allows us to render some text in the chatview itself, similar to CLE events
|
||||
const summarizationRequest = createSummarizationRequestMessage(
|
||||
messages,
|
||||
'Summarize the session and begin a new one'
|
||||
);
|
||||
|
||||
// add the message to the message thread
|
||||
setMessages([...messages, summarizationRequest]);
|
||||
};
|
||||
|
||||
const updateSummary = (newSummaryContent: string) => {
|
||||
// Update the summary content
|
||||
setSummaryContent(newSummaryContent);
|
||||
|
||||
// Update the thread if it exists
|
||||
if (summarizedThread.length > 0) {
|
||||
// Create a deep copy of the thread
|
||||
const updatedThread = [...summarizedThread];
|
||||
|
||||
// Create a copy of the first message
|
||||
const firstMessage = { ...updatedThread[0] };
|
||||
|
||||
// Create a copy of the content array
|
||||
const updatedContent = [...firstMessage.content];
|
||||
|
||||
// Update the summary text in the first content item
|
||||
if (updatedContent[0] && updatedContent[0].type === 'text') {
|
||||
updatedContent[0] = {
|
||||
...updatedContent[0],
|
||||
text: newSummaryContent,
|
||||
};
|
||||
}
|
||||
|
||||
// Update the message with the new content
|
||||
firstMessage.content = updatedContent;
|
||||
updatedThread[0] = firstMessage;
|
||||
|
||||
// Update the thread
|
||||
setSummarizedThread(updatedThread);
|
||||
}
|
||||
};
|
||||
|
||||
const resetMessagesWithSummary = (
|
||||
messages: Message[],
|
||||
setMessages: (messages: Message[]) => void,
|
||||
ancestorMessages: Message[],
|
||||
setAncestorMessages: (messages: Message[]) => void,
|
||||
summaryContent: string
|
||||
) => {
|
||||
// Create a copy of the summarized thread
|
||||
const updatedSummarizedThread = [...summarizedThread];
|
||||
|
||||
// Make sure there's at least one message in the summarized thread
|
||||
if (updatedSummarizedThread.length > 0) {
|
||||
// Get the first message
|
||||
const firstMessage = { ...updatedSummarizedThread[0] };
|
||||
|
||||
// Make a copy of the content array
|
||||
const contentCopy = [...firstMessage.content];
|
||||
|
||||
// Assuming the first content item is of type TextContent
|
||||
if (contentCopy.length > 0 && 'text' in contentCopy[0]) {
|
||||
// Update the text with the new summary content
|
||||
contentCopy[0] = {
|
||||
...contentCopy[0],
|
||||
text: summaryContent,
|
||||
};
|
||||
|
||||
// Update the first message with the new content
|
||||
firstMessage.content = contentCopy;
|
||||
|
||||
// Update the first message in the thread
|
||||
updatedSummarizedThread[0] = firstMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// Update metadata for the summarized thread
|
||||
const finalUpdatedThread = updatedSummarizedThread.map((msg, index) => ({
|
||||
...msg,
|
||||
display: index === 0, // First message has display: true, others false
|
||||
sendToLLM: true, // All messages have sendToLLM: true
|
||||
}));
|
||||
|
||||
// Update the messages state
|
||||
setMessages(finalUpdatedThread);
|
||||
|
||||
// If ancestorMessages already has items, extend it instead of replacing it
|
||||
if (ancestorMessages.length > 0) {
|
||||
// Convert current messages to ancestor format
|
||||
const newAncestorMessages = messages.map((msg) => ({
|
||||
...msg,
|
||||
display: true,
|
||||
sendToLLM: false,
|
||||
}));
|
||||
|
||||
// Append new ancestor messages to existing ones
|
||||
setAncestorMessages([...ancestorMessages, ...newAncestorMessages]);
|
||||
} else {
|
||||
// Initial set of ancestor messages
|
||||
const newAncestorMessages = messages.map((msg) => ({
|
||||
...msg,
|
||||
display: true,
|
||||
sendToLLM: false,
|
||||
}));
|
||||
|
||||
setAncestorMessages(newAncestorMessages);
|
||||
}
|
||||
|
||||
// Clear the summarized thread and content
|
||||
setSummarizedThread([]);
|
||||
setSummaryContent('');
|
||||
};
|
||||
|
||||
const hasContextHandlerContent = (message: Message): boolean => {
|
||||
return hasContextLengthExceededContent(message) || hasSummarizationRequestedContent(message);
|
||||
};
|
||||
|
||||
const hasContextLengthExceededContent = (message: Message): boolean => {
|
||||
return message.content.some((content) => content.type === 'contextLengthExceeded');
|
||||
};
|
||||
|
||||
const hasSummarizationRequestedContent = (message: Message): boolean => {
|
||||
return message.content.some((content) => content.type === 'summarizationRequested');
|
||||
};
|
||||
|
||||
const getContextHandlerType = (
|
||||
message: Message
|
||||
): 'contextLengthExceeded' | 'summarizationRequested' => {
|
||||
if (hasContextLengthExceededContent(message)) {
|
||||
return 'contextLengthExceeded';
|
||||
}
|
||||
return 'summarizationRequested';
|
||||
};
|
||||
|
||||
const openSummaryModal = () => {
|
||||
setIsSummaryModalOpen(true);
|
||||
};
|
||||
|
||||
const closeSummaryModal = () => {
|
||||
setIsSummaryModalOpen(false);
|
||||
};
|
||||
|
||||
const value = {
|
||||
// State
|
||||
summaryContent,
|
||||
summarizedThread,
|
||||
isSummaryModalOpen,
|
||||
isLoadingSummary,
|
||||
errorLoadingSummary,
|
||||
preparingManualSummary,
|
||||
|
||||
// Actions
|
||||
updateSummary,
|
||||
resetMessagesWithSummary,
|
||||
openSummaryModal,
|
||||
closeSummaryModal,
|
||||
hasContextHandlerContent,
|
||||
hasContextLengthExceededContent,
|
||||
hasSummarizationRequestedContent,
|
||||
getContextHandlerType,
|
||||
handleContextLengthExceeded,
|
||||
handleManualSummarization,
|
||||
};
|
||||
|
||||
return (
|
||||
<ChatContextManagerContext.Provider value={value}>
|
||||
{children}
|
||||
</ChatContextManagerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// Create a hook to use the context
|
||||
export const useChatContextManager = () => {
|
||||
const context = useContext(ChatContextManagerContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useContextManager must be used within a ContextManagerProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Reference in New Issue
Block a user