您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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