Skip to content

Commit 4c4093c

Browse files
committed
feat: add response version pagination for regenerate. Persist regenerated outputs on each assistant message and add previous/next navigation so users can compare alternate regenerated responses in place.
1 parent c64d6ba commit 4c4093c

File tree

2 files changed

+270
-54
lines changed

2 files changed

+270
-54
lines changed

src/components/Bot.tsx

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,24 @@ export type AgentFlowExecutedData = {
116116
status?: ExecutionState;
117117
};
118118

119+
export type MessageResponseVersion = {
120+
message?: string;
121+
messageId?: string;
122+
id?: string;
123+
sourceDocuments?: any;
124+
fileAnnotations?: any;
125+
agentReasoning?: IAgentReasoning[];
126+
agentFlowExecutedData?: any;
127+
usedTools?: any[];
128+
action?: IAction | null;
129+
artifacts?: Partial<FileUpload>[];
130+
thinking?: string;
131+
thinkingDuration?: number;
132+
isThinking?: boolean;
133+
rating?: FeedbackRatingType;
134+
dateTime?: string;
135+
};
136+
119137
export type MessageType = {
120138
messageId?: string;
121139
message: string;
@@ -137,6 +155,8 @@ export type MessageType = {
137155
thinking?: string;
138156
thinkingDuration?: number;
139157
isThinking?: boolean;
158+
responseVersions?: MessageResponseVersion[];
159+
responseVersionIndex?: number;
140160
};
141161

142162
type IUploads = {
@@ -496,6 +516,7 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
496516
const [isMessageStopping, setIsMessageStopping] = createSignal(false);
497517
const [starterPrompts, setStarterPrompts] = createSignal<string[]>([], { equals: false });
498518
const [chatFeedbackStatus, setChatFeedbackStatus] = createSignal<boolean>(false);
519+
const [chatFeedbackRegenerateResponseStatus, setChatFeedbackRegenerateResponseStatus] = createSignal<boolean>(false);
499520
const [fullFileUpload, setFullFileUpload] = createSignal<boolean>(false);
500521
const [uploadsConfig, setUploadsConfig] = createSignal<UploadsConfig>();
501522
const [leadsConfig, setLeadsConfig] = createSignal<LeadsConfig>();
@@ -881,6 +902,51 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
881902
handleSubmit(prompt);
882903
};
883904

905+
const createResponseVersion = (message: Partial<MessageType>): MessageResponseVersion => ({
906+
message: message.message ?? '',
907+
messageId: message.messageId,
908+
id: message.id,
909+
sourceDocuments: message.sourceDocuments,
910+
fileAnnotations: message.fileAnnotations,
911+
agentReasoning: message.agentReasoning,
912+
agentFlowExecutedData: message.agentFlowExecutedData,
913+
usedTools: message.usedTools,
914+
action: message.action,
915+
artifacts: message.artifacts,
916+
thinking: message.thinking,
917+
thinkingDuration: message.thinkingDuration,
918+
isThinking: message.isThinking,
919+
rating: message.rating,
920+
dateTime: message.dateTime,
921+
});
922+
923+
const parseConfigBoolean = (value: unknown, defaultValue: boolean) => {
924+
if (typeof value === 'boolean') return value;
925+
if (typeof value === 'string') {
926+
const normalized = value.trim().toLowerCase();
927+
if (normalized === 'true') return true;
928+
if (normalized === 'false') return false;
929+
}
930+
return defaultValue;
931+
};
932+
933+
const getLastApiMessageIndex = () => {
934+
const currentMessages = messages();
935+
for (let i = currentMessages.length - 1; i >= 0; i--) {
936+
if (currentMessages[i].type === 'apiMessage') return i;
937+
}
938+
return -1;
939+
};
940+
941+
const canRegenerateResponse = (messageIndex: number) => {
942+
if (!chatFeedbackStatus() || !chatFeedbackRegenerateResponseStatus() || loading()) return false;
943+
if (messageIndex !== getLastApiMessageIndex()) return false;
944+
const previousMessage = messages()[messageIndex - 1];
945+
if (!previousMessage || previousMessage.type !== 'userMessage') return false;
946+
if (previousMessage.fileUploads?.length) return false;
947+
return true;
948+
};
949+
884950
const handleRegenerateResponse = async (messageIndex: number) => {
885951
if (loading()) return;
886952
if (previews().length) return;
@@ -893,14 +959,22 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
893959
const previousMessage = currentMessages[messageIndex - 1];
894960
if (!previousMessage || previousMessage.type !== 'userMessage' || previousMessage.fileUploads?.length) return;
895961

962+
const existingResponseVersions =
963+
targetMessage.responseVersions && targetMessage.responseVersions.length > 0
964+
? [...targetMessage.responseVersions]
965+
: [createResponseVersion(targetMessage)];
966+
896967
setFollowUpPrompts([]);
897968
const updatedMessages = currentMessages.slice(0, messageIndex);
898969
addChatMessage(updatedMessages);
899970
setMessages(updatedMessages);
900971

901972
// Note: chatId is kept so the server retains conversation context up to this point.
902973
// The server's history will still include messages that were removed client-side
903-
await handleSubmit(previousMessage.message, undefined, undefined, { skipAddUserMessage: true });
974+
await handleSubmit(previousMessage.message, undefined, undefined, {
975+
skipAddUserMessage: true,
976+
responseVersions: existingResponseVersions,
977+
});
904978
};
905979

906980
const updateMetadata = (data: any, input: string) => {
@@ -945,7 +1019,11 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
9451019
}
9461020
};
9471021

948-
const fetchResponseFromEventStream = async (chatflowid: string, params: any) => {
1022+
const fetchResponseFromEventStream = async (
1023+
chatflowid: string,
1024+
params: any,
1025+
options?: { skipAddUserMessage?: boolean; responseVersions?: MessageResponseVersion[] },
1026+
) => {
9491027
const chatId = params.chatId;
9501028
const input = params.question;
9511029
params.streaming = true;
@@ -988,7 +1066,15 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
9881066
const payload = JSON.parse(ev.data);
9891067
switch (payload.event) {
9901068
case 'start':
991-
setMessages((prevMessages) => [...prevMessages, { message: '', type: 'apiMessage' }]);
1069+
setMessages((prevMessages) => {
1070+
const newMessage: MessageType = { message: '', type: 'apiMessage' };
1071+
if (options?.responseVersions && options.responseVersions.length > 0) {
1072+
const responseVersions = [...options.responseVersions, createResponseVersion(newMessage)];
1073+
newMessage.responseVersions = responseVersions;
1074+
newMessage.responseVersionIndex = responseVersions.length - 1;
1075+
}
1076+
return [...prevMessages, newMessage];
1077+
});
9921078
break;
9931079
case 'token':
9941080
updateLastMessage(payload.data);
@@ -1192,7 +1278,7 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
11921278
value: string | object,
11931279
action?: IAction | undefined | null,
11941280
humanInput?: any,
1195-
options?: { skipAddUserMessage?: boolean },
1281+
options?: { skipAddUserMessage?: boolean; responseVersions?: MessageResponseVersion[] },
11961282
) => {
11971283
if (typeof value === 'string' && value.trim() === '') {
11981284
const containsFile = previews().filter((item) => !item.mime.startsWith('image') && item.type !== 'audio').length > 0;
@@ -1259,7 +1345,7 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
12591345
if (humanInput) body.humanInput = humanInput;
12601346

12611347
if (isChatFlowAvailableToStream()) {
1262-
fetchResponseFromEventStream(props.chatflowid, body);
1348+
fetchResponseFromEventStream(props.chatflowid, body, options);
12631349
} else {
12641350
const result = await sendMessageQuery({
12651351
chatflowid: props.chatflowid,
@@ -1281,7 +1367,7 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
12811367
playReceiveSound();
12821368

12831369
setMessages((prevMessages) => {
1284-
const newMessage = {
1370+
const baseResponseMessage = {
12851371
message: text,
12861372
id: data?.chatMessageId,
12871373
sourceDocuments: data?.sourceDocuments,
@@ -1297,6 +1383,21 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
12971383
feedback: null,
12981384
dateTime: new Date().toISOString(),
12991385
};
1386+
1387+
const responseVersions =
1388+
options?.responseVersions && options.responseVersions.length > 0
1389+
? [...options.responseVersions, createResponseVersion(baseResponseMessage)]
1390+
: undefined;
1391+
1392+
const newMessage = {
1393+
...baseResponseMessage,
1394+
...(responseVersions
1395+
? {
1396+
responseVersions,
1397+
responseVersionIndex: responseVersions.length - 1,
1398+
}
1399+
: {}),
1400+
};
13001401
const allMessages = [...prevMessages, newMessage];
13011402
addChatMessage(allMessages);
13021403
return allMessages;
@@ -1491,6 +1592,14 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
14911592
rating: message.rating,
14921593
dateTime: message.dateTime,
14931594
};
1595+
if (message.responseVersionIndex !== undefined) chatHistory.responseVersionIndex = message.responseVersionIndex;
1596+
if ((message as any).responseVersions) {
1597+
const responseVersions =
1598+
typeof (message as any).responseVersions === 'string'
1599+
? JSON.parse((message as any).responseVersions)
1600+
: (message as any).responseVersions;
1601+
if (Array.isArray(responseVersions)) chatHistory.responseVersions = responseVersions;
1602+
}
14941603
if (message.sourceDocuments) chatHistory.sourceDocuments = message.sourceDocuments;
14951604
if (message.fileAnnotations) chatHistory.fileAnnotations = message.fileAnnotations;
14961605
if (message.fileUploads) chatHistory.fileUploads = message.fileUploads;
@@ -2633,6 +2742,7 @@ export const Bot = (botProps: BotProps & { class?: string }) => {
26332742
avatarSrc={props.botMessage?.avatarSrc}
26342743
chatFeedbackStatus={chatFeedbackStatus()}
26352744
onRegenerateResponse={() => handleRegenerateResponse(index())}
2745+
showRegenerateResponseButton={canRegenerateResponse(index())}
26362746
fontSize={props.fontSize}
26372747
isLoading={loading() && index() === messages().length - 1}
26382748
showAgentMessages={props.showAgentMessages}

0 commit comments

Comments
 (0)