Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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