123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- import { UUID_NIL } from './constants'
- import type { IChatItem } from './chat/type'
- import type { ChatItem, ChatItemInTree } from './types'
- 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)
- }
- async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> {
- const urlParams = new URLSearchParams(window.location.search)
- const inputs: Record<string, any> = {}
- const entriesArray = Array.from(urlParams.entries())
- await Promise.all(
- entriesArray.map(async ([key, value]) => {
- inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value))
- }),
- )
- return inputs
- }
- function isValidGeneratedAnswer(item?: ChatItem | ChatItemInTree): boolean {
- return !!item && item.isAnswer && !item.id.startsWith('answer-placeholder-') && !item.isOpeningStatement
- }
- function getLastAnswer<T extends ChatItem | ChatItemInTree>(chatList: T[]): T | null {
- for (let i = chatList.length - 1; i >= 0; i--) {
- const item = chatList[i]
- if (isValidGeneratedAnswer(item))
- return item
- }
- return null
- }
- /**
- * Build a chat item tree from a chat list
- * @param allMessages - The chat list, sorted from oldest to newest
- * @returns The chat item tree
- */
- 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
- || !allMessages.some(item => item.id === parentMessageId) // parent message might not be fetched yet, in this case we will append the question to the root nodes
- )
- rootNodes.push(questionNode)
- else
- map[parentMessageId]?.children!.push(questionNode)
- }
- }
- 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.slice().reverse().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,
- isValidGeneratedAnswer,
- getLastAnswer,
- buildChatItemTree,
- getThreadMessages,
- }
|