MultiResponseMessages.svelte 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <script lang="ts">
  2. import dayjs from 'dayjs';
  3. import { onMount, tick, getContext } from 'svelte';
  4. import { createEventDispatcher } from 'svelte';
  5. import { mobile, settings } from '$lib/stores';
  6. import { generateMoACompletion } from '$lib/apis';
  7. import { updateChatById } from '$lib/apis/chats';
  8. import { createOpenAITextStream } from '$lib/apis/streaming';
  9. import ResponseMessage from './ResponseMessage.svelte';
  10. import Tooltip from '$lib/components/common/Tooltip.svelte';
  11. import Merge from '$lib/components/icons/Merge.svelte';
  12. import Markdown from './Markdown.svelte';
  13. import Name from './Name.svelte';
  14. import Skeleton from './Skeleton.svelte';
  15. const i18n = getContext('i18n');
  16. export let chatId;
  17. export let history;
  18. export let messages = [];
  19. export let messageIdx;
  20. export let parentMessage;
  21. export let isLastMessage;
  22. export let readOnly = false;
  23. export let updateChatMessages: Function;
  24. export let confirmEditResponseMessage: Function;
  25. export let rateMessage: Function;
  26. export let copyToClipboard: Function;
  27. export let continueGeneration: Function;
  28. export let mergeResponses: Function;
  29. export let regenerateResponse: Function;
  30. const dispatch = createEventDispatcher();
  31. let currentMessageId;
  32. let groupedMessages = {};
  33. let groupedMessagesIdx = {};
  34. $: if (parentMessage) {
  35. initHandler();
  36. }
  37. const showPreviousMessage = (modelIdx) => {
  38. groupedMessagesIdx[modelIdx] = Math.max(0, groupedMessagesIdx[modelIdx] - 1);
  39. let messageId = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]].id;
  40. console.log(messageId);
  41. let messageChildrenIds = history.messages[messageId].childrenIds;
  42. while (messageChildrenIds.length !== 0) {
  43. messageId = messageChildrenIds.at(-1);
  44. messageChildrenIds = history.messages[messageId].childrenIds;
  45. }
  46. history.currentId = messageId;
  47. dispatch('change');
  48. };
  49. const showNextMessage = (modelIdx) => {
  50. groupedMessagesIdx[modelIdx] = Math.min(
  51. groupedMessages[modelIdx].messages.length - 1,
  52. groupedMessagesIdx[modelIdx] + 1
  53. );
  54. let messageId = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]].id;
  55. console.log(messageId);
  56. let messageChildrenIds = history.messages[messageId].childrenIds;
  57. while (messageChildrenIds.length !== 0) {
  58. messageId = messageChildrenIds.at(-1);
  59. messageChildrenIds = history.messages[messageId].childrenIds;
  60. }
  61. history.currentId = messageId;
  62. dispatch('change');
  63. };
  64. const initHandler = async () => {
  65. await tick();
  66. currentMessageId = messages[messageIdx].id;
  67. groupedMessages = parentMessage?.models.reduce((a, model, modelIdx) => {
  68. // Find all messages that are children of the parent message and have the same model
  69. const modelMessages = parentMessage?.childrenIds
  70. .map((id) => history.messages[id])
  71. .filter((m) => m.modelIdx === modelIdx);
  72. return {
  73. ...a,
  74. [modelIdx]: { messages: modelMessages }
  75. };
  76. }, {});
  77. groupedMessagesIdx = parentMessage?.models.reduce((a, model, modelIdx) => {
  78. const idx = groupedMessages[modelIdx].messages.findIndex((m) => m.id === currentMessageId);
  79. if (idx !== -1) {
  80. return {
  81. ...a,
  82. [modelIdx]: idx
  83. };
  84. } else {
  85. return {
  86. ...a,
  87. [modelIdx]: 0
  88. };
  89. }
  90. }, {});
  91. };
  92. const mergeResponsesHandler = async () => {
  93. const responses = Object.keys(groupedMessages).map((modelIdx) => {
  94. const { messages } = groupedMessages[modelIdx];
  95. return messages[groupedMessagesIdx[modelIdx]].content;
  96. });
  97. mergeResponses(currentMessageId, responses, chatId);
  98. };
  99. onMount(async () => {
  100. initHandler();
  101. });
  102. </script>
  103. <div>
  104. <div
  105. class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
  106. id="responses-container-{chatId}-{parentMessage.id}"
  107. >
  108. {#key currentMessageId}
  109. {#each Object.keys(groupedMessages) as modelIdx}
  110. {#if groupedMessagesIdx[modelIdx] !== undefined && groupedMessages[modelIdx].messages.length > 0}
  111. <!-- svelte-ignore a11y-no-static-element-interactions -->
  112. <!-- svelte-ignore a11y-click-events-have-key-events -->
  113. {@const message = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]]}
  114. <div
  115. class=" snap-center w-full max-w-full m-1 border {history.messages[currentMessageId]
  116. ?.modelIdx == modelIdx
  117. ? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
  118. $mobile ? 'min-w-full' : 'min-w-[32rem]'
  119. }`
  120. : `border-gray-50 dark:border-gray-850 border-dashed ${
  121. $mobile ? 'min-w-full' : 'min-w-80'
  122. }`} transition-all p-5 rounded-2xl"
  123. on:click={() => {
  124. if (currentMessageId != message.id) {
  125. currentMessageId = message.id;
  126. let messageId = message.id;
  127. console.log(messageId);
  128. //
  129. let messageChildrenIds = history.messages[messageId].childrenIds;
  130. while (messageChildrenIds.length !== 0) {
  131. messageId = messageChildrenIds.at(-1);
  132. messageChildrenIds = history.messages[messageId].childrenIds;
  133. }
  134. history.currentId = messageId;
  135. dispatch('change');
  136. }
  137. }}
  138. >
  139. {#key history.currentId}
  140. {#if message}
  141. <ResponseMessage
  142. {message}
  143. siblings={groupedMessages[modelIdx].messages.map((m) => m.id)}
  144. isLastMessage={true}
  145. {updateChatMessages}
  146. {confirmEditResponseMessage}
  147. showPreviousMessage={() => showPreviousMessage(modelIdx)}
  148. showNextMessage={() => showNextMessage(modelIdx)}
  149. {readOnly}
  150. {rateMessage}
  151. {copyToClipboard}
  152. {continueGeneration}
  153. regenerateResponse={async (message) => {
  154. regenerateResponse(message);
  155. await tick();
  156. groupedMessagesIdx[modelIdx] = groupedMessages[modelIdx].messages.length - 1;
  157. }}
  158. on:save={async (e) => {
  159. console.log('save', e);
  160. const message = e.detail;
  161. history.messages[message.id] = message;
  162. await updateChatById(localStorage.token, chatId, {
  163. messages: messages,
  164. history: history
  165. });
  166. }}
  167. />
  168. {/if}
  169. {/key}
  170. </div>
  171. {/if}
  172. {/each}
  173. {/key}
  174. </div>
  175. {#if !readOnly && isLastMessage}
  176. {#if !Object.keys(groupedMessages).find((modelIdx) => {
  177. const { messages } = groupedMessages[modelIdx];
  178. return !messages[groupedMessagesIdx[modelIdx]].done;
  179. })}
  180. <div class="flex justify-end">
  181. <div class="w-full">
  182. {#if history.messages[currentMessageId]?.merged?.status}
  183. {@const message = history.messages[currentMessageId]?.merged}
  184. <div class="w-full rounded-xl pl-5 pr-2 py-2">
  185. <Name>
  186. Merged Response
  187. {#if message.timestamp}
  188. <span
  189. class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
  190. >
  191. {dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
  192. </span>
  193. {/if}
  194. </Name>
  195. <div class="mt-1 markdown-prose w-full min-w-full">
  196. {#if (message?.content ?? '') === ''}
  197. <Skeleton />
  198. {:else}
  199. <Markdown id={`merged`} content={message.content ?? ''} />
  200. {/if}
  201. </div>
  202. </div>
  203. {/if}
  204. </div>
  205. <div class=" flex-shrink-0 text-gray-600 dark:text-gray-500 mt-1">
  206. <Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
  207. <button
  208. type="button"
  209. id="merge-response-button"
  210. class="{true
  211. ? 'visible'
  212. : 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
  213. on:click={() => {
  214. mergeResponsesHandler();
  215. }}
  216. >
  217. <Merge className=" size-5 " />
  218. </button>
  219. </Tooltip>
  220. </div>
  221. </div>
  222. {/if}
  223. {/if}
  224. </div>