Spaces:
Build error
Build error
import { addFileInfos, sortAgentSorts } from '../../tools/utils' | |
import { UUID_NIL } from './constants' | |
import type { IChatItem } from './chat/type' | |
import type { ChatItem, ChatItemInTree } from './types' | |
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' | |
async function decodeBase64AndDecompress(base64String: string) { | |
const binaryString = atob(base64String) | |
const compressedUint8Array = Uint8Array.from(binaryString, char => char.charCodeAt(0)) | |
const decompressedStream = new Response(compressedUint8Array).body?.pipeThrough(new DecompressionStream('gzip')) | |
const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer() | |
return new TextDecoder().decode(decompressedArrayBuffer) | |
} | |
function getProcessedInputsFromUrlParams(): Record<string, any> { | |
const urlParams = new URLSearchParams(window.location.search) | |
const inputs: Record<string, any> = {} | |
urlParams.forEach(async (value, key) => { | |
inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value)) | |
}) | |
return inputs | |
} | |
function getLastAnswer(chatList: ChatItem[]) { | |
for (let i = chatList.length - 1; i >= 0; i--) { | |
const item = chatList[i] | |
if (item.isAnswer && !item.id.startsWith('answer-placeholder-') && !item.isOpeningStatement) | |
return item | |
} | |
return null | |
} | |
function appendQAToChatList(chatList: ChatItem[], item: any) { | |
// we append answer first and then question since will reverse the whole chatList later | |
const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] | |
chatList.push({ | |
id: item.id, | |
content: item.answer, | |
agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files), | |
feedback: item.feedback, | |
isAnswer: true, | |
citation: item.retriever_resources, | |
message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))), | |
}) | |
const questionFiles = item.message_files?.filter((file: any) => file.belongs_to === 'user') || [] | |
chatList.push({ | |
id: `question-${item.id}`, | |
content: item.query, | |
isAnswer: false, | |
message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))), | |
}) | |
} | |
/** | |
* Computes the latest thread messages from all messages of the conversation. | |
* Same logic as backend codebase `api/core/prompt/utils/extract_thread_messages.py` | |
* | |
* @param fetchedMessages - The history chat list data from the backend, sorted by created_at in descending order. This includes all flattened history messages of the conversation. | |
* @returns An array of ChatItems representing the latest thread. | |
*/ | |
function getPrevChatList(fetchedMessages: any[]) { | |
const ret: ChatItem[] = [] | |
let nextMessageId = null | |
for (const item of fetchedMessages) { | |
if (!item.parent_message_id) { | |
appendQAToChatList(ret, item) | |
break | |
} | |
if (!nextMessageId) { | |
appendQAToChatList(ret, item) | |
nextMessageId = item.parent_message_id | |
} | |
else { | |
if (item.id === nextMessageId || nextMessageId === UUID_NIL) { | |
appendQAToChatList(ret, item) | |
nextMessageId = item.parent_message_id | |
} | |
} | |
} | |
return ret.reverse() | |
} | |
function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { | |
const map: Record<string, ChatItemInTree> = {} | |
const rootNodes: ChatItemInTree[] = [] | |
const childrenCount: Record<string, number> = {} | |
let lastAppendedLegacyAnswer: ChatItemInTree | null = null | |
for (let i = 0; i < allMessages.length; i += 2) { | |
const question = allMessages[i]! | |
const answer = allMessages[i + 1]! | |
const isLegacy = question.parentMessageId === UUID_NIL | |
const parentMessageId = isLegacy | |
? (lastAppendedLegacyAnswer?.id || '') | |
: (question.parentMessageId || '') | |
// Process question | |
childrenCount[parentMessageId] = (childrenCount[parentMessageId] || 0) + 1 | |
const questionNode: ChatItemInTree = { | |
...question, | |
children: [], | |
} | |
map[question.id] = questionNode | |
// Process answer | |
childrenCount[question.id] = 1 | |
const answerNode: ChatItemInTree = { | |
...answer, | |
children: [], | |
siblingIndex: isLegacy ? 0 : childrenCount[parentMessageId] - 1, | |
} | |
map[answer.id] = answerNode | |
// Connect question and answer | |
questionNode.children!.push(answerNode) | |
// Append to parent or add to root | |
if (isLegacy) { | |
if (!lastAppendedLegacyAnswer) | |
rootNodes.push(questionNode) | |
else | |
lastAppendedLegacyAnswer.children!.push(questionNode) | |
lastAppendedLegacyAnswer = answerNode | |
} | |
else { | |
if (!parentMessageId) | |
rootNodes.push(questionNode) | |
else | |
map[parentMessageId]?.children!.push(questionNode) | |
} | |
} | |
// If no messages have parentMessageId=null (indicating a root node), | |
// then we likely have a partial chat history. In this case, | |
// use the first available message as the root node. | |
if (rootNodes.length === 0 && allMessages.length > 0) | |
rootNodes.push(map[allMessages[0]!.id]!) | |
return rootNodes | |
} | |
function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): ChatItemInTree[] { | |
let ret: ChatItemInTree[] = [] | |
let targetNode: ChatItemInTree | undefined | |
// find path to the target message | |
const stack = tree.toReversed().map(rootNode => ({ | |
node: rootNode, | |
path: [rootNode], | |
})) | |
while (stack.length > 0) { | |
const { node, path } = stack.pop()! | |
if ( | |
node.id === targetMessageId | |
|| (!targetMessageId && !node.children?.length && !stack.length) // if targetMessageId is not provided, we use the last message in the tree as the target | |
) { | |
targetNode = node | |
ret = path.map((item, index) => { | |
if (!item.isAnswer) | |
return item | |
const parentAnswer = path[index - 2] | |
const siblingCount = !parentAnswer ? tree.length : parentAnswer.children!.length | |
const prevSibling = !parentAnswer ? tree[item.siblingIndex! - 1]?.children?.[0]?.id : parentAnswer.children![item.siblingIndex! - 1]?.children?.[0].id | |
const nextSibling = !parentAnswer ? tree[item.siblingIndex! + 1]?.children?.[0]?.id : parentAnswer.children![item.siblingIndex! + 1]?.children?.[0].id | |
return { ...item, siblingCount, prevSibling, nextSibling } | |
}) | |
break | |
} | |
if (node.children) { | |
for (let i = node.children.length - 1; i >= 0; i--) { | |
stack.push({ | |
node: node.children[i], | |
path: [...path, node.children[i]], | |
}) | |
} | |
} | |
} | |
// append all descendant messages to the path | |
if (targetNode) { | |
const stack = [targetNode] | |
while (stack.length > 0) { | |
const node = stack.pop()! | |
if (node !== targetNode) | |
ret.push(node) | |
if (node.children?.length) { | |
const lastChild = node.children.at(-1)! | |
if (!lastChild.isAnswer) { | |
stack.push(lastChild) | |
continue | |
} | |
const parentAnswer = ret.at(-2) | |
const siblingCount = parentAnswer?.children?.length | |
const prevSibling = parentAnswer?.children?.at(-2)?.children?.[0]?.id | |
stack.push({ ...lastChild, siblingCount, prevSibling }) | |
} | |
} | |
} | |
return ret | |
} | |
export { | |
getProcessedInputsFromUrlParams, | |
getPrevChatList, | |
getLastAnswer, | |
buildChatItemTree, | |
getThreadMessages, | |
} | |