utils.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { UUID_NIL } from './constants'
  2. import type { IChatItem } from './chat/type'
  3. import type { ChatItem, ChatItemInTree } from './types'
  4. async function decodeBase64AndDecompress(base64String: string) {
  5. const binaryString = atob(base64String)
  6. const compressedUint8Array = Uint8Array.from(binaryString, char => char.charCodeAt(0))
  7. const decompressedStream = new Response(compressedUint8Array).body?.pipeThrough(new DecompressionStream('gzip'))
  8. const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer()
  9. return new TextDecoder().decode(decompressedArrayBuffer)
  10. }
  11. function getProcessedInputsFromUrlParams(): Record<string, any> {
  12. const urlParams = new URLSearchParams(window.location.search)
  13. const inputs: Record<string, any> = {}
  14. urlParams.forEach(async (value, key) => {
  15. inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value))
  16. })
  17. return inputs
  18. }
  19. function isValidGeneratedAnswer(item?: ChatItem | ChatItemInTree): boolean {
  20. return !!item && item.isAnswer && !item.id.startsWith('answer-placeholder-') && !item.isOpeningStatement
  21. }
  22. function getLastAnswer<T extends ChatItem | ChatItemInTree>(chatList: T[]): T | null {
  23. for (let i = chatList.length - 1; i >= 0; i--) {
  24. const item = chatList[i]
  25. if (isValidGeneratedAnswer(item))
  26. return item
  27. }
  28. return null
  29. }
  30. /**
  31. * Build a chat item tree from a chat list
  32. * @param allMessages - The chat list, sorted from oldest to newest
  33. * @returns The chat item tree
  34. */
  35. function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] {
  36. const map: Record<string, ChatItemInTree> = {}
  37. const rootNodes: ChatItemInTree[] = []
  38. const childrenCount: Record<string, number> = {}
  39. let lastAppendedLegacyAnswer: ChatItemInTree | null = null
  40. for (let i = 0; i < allMessages.length; i += 2) {
  41. const question = allMessages[i]!
  42. const answer = allMessages[i + 1]!
  43. const isLegacy = question.parentMessageId === UUID_NIL
  44. const parentMessageId = isLegacy
  45. ? (lastAppendedLegacyAnswer?.id || '')
  46. : (question.parentMessageId || '')
  47. // Process question
  48. childrenCount[parentMessageId] = (childrenCount[parentMessageId] || 0) + 1
  49. const questionNode: ChatItemInTree = {
  50. ...question,
  51. children: [],
  52. }
  53. map[question.id] = questionNode
  54. // Process answer
  55. childrenCount[question.id] = 1
  56. const answerNode: ChatItemInTree = {
  57. ...answer,
  58. children: [],
  59. siblingIndex: isLegacy ? 0 : childrenCount[parentMessageId] - 1,
  60. }
  61. map[answer.id] = answerNode
  62. // Connect question and answer
  63. questionNode.children!.push(answerNode)
  64. // Append to parent or add to root
  65. if (isLegacy) {
  66. if (!lastAppendedLegacyAnswer)
  67. rootNodes.push(questionNode)
  68. else
  69. lastAppendedLegacyAnswer.children!.push(questionNode)
  70. lastAppendedLegacyAnswer = answerNode
  71. }
  72. else {
  73. if (
  74. !parentMessageId
  75. || !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
  76. )
  77. rootNodes.push(questionNode)
  78. else
  79. map[parentMessageId]?.children!.push(questionNode)
  80. }
  81. }
  82. return rootNodes
  83. }
  84. function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): ChatItemInTree[] {
  85. let ret: ChatItemInTree[] = []
  86. let targetNode: ChatItemInTree | undefined
  87. // find path to the target message
  88. const stack = tree.slice().reverse().map(rootNode => ({
  89. node: rootNode,
  90. path: [rootNode],
  91. }))
  92. while (stack.length > 0) {
  93. const { node, path } = stack.pop()!
  94. if (
  95. node.id === targetMessageId
  96. || (!targetMessageId && !node.children?.length && !stack.length) // if targetMessageId is not provided, we use the last message in the tree as the target
  97. ) {
  98. targetNode = node
  99. ret = path.map((item, index) => {
  100. if (!item.isAnswer)
  101. return item
  102. const parentAnswer = path[index - 2]
  103. const siblingCount = !parentAnswer ? tree.length : parentAnswer.children!.length
  104. const prevSibling = !parentAnswer ? tree[item.siblingIndex! - 1]?.children?.[0]?.id : parentAnswer.children![item.siblingIndex! - 1]?.children?.[0].id
  105. const nextSibling = !parentAnswer ? tree[item.siblingIndex! + 1]?.children?.[0]?.id : parentAnswer.children![item.siblingIndex! + 1]?.children?.[0].id
  106. return { ...item, siblingCount, prevSibling, nextSibling }
  107. })
  108. break
  109. }
  110. if (node.children) {
  111. for (let i = node.children.length - 1; i >= 0; i--) {
  112. stack.push({
  113. node: node.children[i],
  114. path: [...path, node.children[i]],
  115. })
  116. }
  117. }
  118. }
  119. // append all descendant messages to the path
  120. if (targetNode) {
  121. const stack = [targetNode]
  122. while (stack.length > 0) {
  123. const node = stack.pop()!
  124. if (node !== targetNode)
  125. ret.push(node)
  126. if (node.children?.length) {
  127. const lastChild = node.children.at(-1)!
  128. if (!lastChild.isAnswer) {
  129. stack.push(lastChild)
  130. continue
  131. }
  132. const parentAnswer = ret.at(-2)
  133. const siblingCount = parentAnswer?.children?.length
  134. const prevSibling = parentAnswer?.children?.at(-2)?.children?.[0]?.id
  135. stack.push({ ...lastChild, siblingCount, prevSibling })
  136. }
  137. }
  138. }
  139. return ret
  140. }
  141. export {
  142. getProcessedInputsFromUrlParams,
  143. isValidGeneratedAnswer,
  144. getLastAnswer,
  145. buildChatItemTree,
  146. getThreadMessages,
  147. }