Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

utils.ts 7.6KB

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