MultiResponseMessages.svelte 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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. import localizedFormat from 'dayjs/plugin/localizedFormat';
  16. const i18n = getContext('i18n');
  17. dayjs.extend(localizedFormat);
  18. export let chatId;
  19. export let history;
  20. export let messageId;
  21. export let isLastMessage;
  22. export let readOnly = false;
  23. export let updateChat: Function;
  24. export let editMessage: Function;
  25. export let saveMessage: Function;
  26. export let rateMessage: Function;
  27. export let actionMessage: Function;
  28. export let submitMessage: Function;
  29. export let continueResponse: Function;
  30. export let regenerateResponse: Function;
  31. export let mergeResponses: Function;
  32. export let addMessages: Function;
  33. export let triggerScroll: Function;
  34. const dispatch = createEventDispatcher();
  35. let currentMessageId;
  36. let parentMessage;
  37. let groupedMessageIds = {};
  38. let groupedMessageIdsIdx = {};
  39. let message = JSON.parse(JSON.stringify(history.messages[messageId]));
  40. $: if (history.messages) {
  41. if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
  42. message = JSON.parse(JSON.stringify(history.messages[messageId]));
  43. }
  44. }
  45. const showPreviousMessage = async (modelIdx) => {
  46. groupedMessageIdsIdx[modelIdx] = Math.max(0, groupedMessageIdsIdx[modelIdx] - 1);
  47. let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]];
  48. console.log(messageId);
  49. let messageChildrenIds = history.messages[messageId].childrenIds;
  50. while (messageChildrenIds.length !== 0) {
  51. messageId = messageChildrenIds.at(-1);
  52. messageChildrenIds = history.messages[messageId].childrenIds;
  53. }
  54. history.currentId = messageId;
  55. await tick();
  56. await updateChat();
  57. triggerScroll();
  58. };
  59. const showNextMessage = async (modelIdx) => {
  60. groupedMessageIdsIdx[modelIdx] = Math.min(
  61. groupedMessageIds[modelIdx].messageIds.length - 1,
  62. groupedMessageIdsIdx[modelIdx] + 1
  63. );
  64. let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]];
  65. console.log(messageId);
  66. let messageChildrenIds = history.messages[messageId].childrenIds;
  67. while (messageChildrenIds.length !== 0) {
  68. messageId = messageChildrenIds.at(-1);
  69. messageChildrenIds = history.messages[messageId].childrenIds;
  70. }
  71. history.currentId = messageId;
  72. await tick();
  73. await updateChat();
  74. triggerScroll();
  75. };
  76. const initHandler = async () => {
  77. console.log('multiresponse:initHandler');
  78. await tick();
  79. currentMessageId = messageId;
  80. parentMessage = history.messages[messageId].parentId
  81. ? history.messages[history.messages[messageId].parentId]
  82. : null;
  83. groupedMessageIds = parentMessage?.models.reduce((a, model, modelIdx) => {
  84. // Find all messages that are children of the parent message and have the same model
  85. let modelMessageIds = parentMessage?.childrenIds
  86. .map((id) => history.messages[id])
  87. .filter((m) => m?.modelIdx === modelIdx)
  88. .map((m) => m.id);
  89. // Legacy support for messages that don't have a modelIdx
  90. // Find all messages that are children of the parent message and have the same model
  91. if (modelMessageIds.length === 0) {
  92. let modelMessages = parentMessage?.childrenIds
  93. .map((id) => history.messages[id])
  94. .filter((m) => m?.model === model);
  95. modelMessages.forEach((m) => {
  96. m.modelIdx = modelIdx;
  97. });
  98. modelMessageIds = modelMessages.map((m) => m.id);
  99. }
  100. return {
  101. ...a,
  102. [modelIdx]: { messageIds: modelMessageIds }
  103. };
  104. }, {});
  105. groupedMessageIdsIdx = parentMessage?.models.reduce((a, model, modelIdx) => {
  106. const idx = groupedMessageIds[modelIdx].messageIds.findIndex((id) => id === messageId);
  107. if (idx !== -1) {
  108. return {
  109. ...a,
  110. [modelIdx]: idx
  111. };
  112. } else {
  113. return {
  114. ...a,
  115. [modelIdx]: groupedMessageIds[modelIdx].messageIds.length - 1
  116. };
  117. }
  118. }, {});
  119. console.log(groupedMessageIds, groupedMessageIdsIdx);
  120. await tick();
  121. };
  122. const mergeResponsesHandler = async () => {
  123. const responses = Object.keys(groupedMessageIds).map((modelIdx) => {
  124. const { messageIds } = groupedMessageIds[modelIdx];
  125. const messageId = messageIds[groupedMessageIdsIdx[modelIdx]];
  126. return history.messages[messageId].content;
  127. });
  128. mergeResponses(messageId, responses, chatId);
  129. };
  130. onMount(async () => {
  131. await initHandler();
  132. await tick();
  133. const messageElement = document.getElementById(`message-${messageId}`);
  134. if (messageElement) {
  135. messageElement.scrollIntoView({ block: 'start' });
  136. }
  137. });
  138. </script>
  139. {#if parentMessage}
  140. <div>
  141. <div
  142. class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
  143. id="responses-container-{chatId}-{parentMessage.id}"
  144. >
  145. {#each Object.keys(groupedMessageIds) as modelIdx}
  146. {#if groupedMessageIdsIdx[modelIdx] !== undefined && groupedMessageIds[modelIdx].messageIds.length > 0}
  147. <!-- svelte-ignore a11y-no-static-element-interactions -->
  148. <!-- svelte-ignore a11y-click-events-have-key-events -->
  149. {@const _messageId =
  150. groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]}
  151. <div
  152. class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
  153. ?.modelIdx == modelIdx
  154. ? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
  155. $mobile ? 'min-w-full' : 'min-w-80'
  156. }`
  157. : `border-gray-50 dark:border-gray-850 border-dashed ${
  158. $mobile ? 'min-w-full' : 'min-w-80'
  159. }`} transition-all p-5 rounded-2xl"
  160. on:click={async () => {
  161. if (messageId != _messageId) {
  162. let currentMessageId = _messageId;
  163. let messageChildrenIds = history.messages[currentMessageId].childrenIds;
  164. while (messageChildrenIds.length !== 0) {
  165. currentMessageId = messageChildrenIds.at(-1);
  166. messageChildrenIds = history.messages[currentMessageId].childrenIds;
  167. }
  168. history.currentId = currentMessageId;
  169. await tick();
  170. await updateChat();
  171. triggerScroll();
  172. }
  173. }}
  174. >
  175. {#key history.currentId}
  176. {#if message}
  177. <ResponseMessage
  178. {chatId}
  179. {history}
  180. messageId={_messageId}
  181. isLastMessage={true}
  182. siblings={groupedMessageIds[modelIdx].messageIds}
  183. showPreviousMessage={() => showPreviousMessage(modelIdx)}
  184. showNextMessage={() => showNextMessage(modelIdx)}
  185. {updateChat}
  186. {editMessage}
  187. {saveMessage}
  188. {rateMessage}
  189. {actionMessage}
  190. {submitMessage}
  191. {continueResponse}
  192. regenerateResponse={async (message) => {
  193. regenerateResponse(message);
  194. await tick();
  195. groupedMessageIdsIdx[modelIdx] =
  196. groupedMessageIds[modelIdx].messageIds.length - 1;
  197. }}
  198. {addMessages}
  199. {readOnly}
  200. />
  201. {/if}
  202. {/key}
  203. </div>
  204. {/if}
  205. {/each}
  206. </div>
  207. {#if !readOnly}
  208. {#if !Object.keys(groupedMessageIds).find((modelIdx) => {
  209. const { messageIds } = groupedMessageIds[modelIdx];
  210. const _messageId = messageIds[groupedMessageIdsIdx[modelIdx]];
  211. return !history.messages[_messageId]?.done ?? false;
  212. })}
  213. <div class="flex justify-end">
  214. <div class="w-full">
  215. {#if history.messages[messageId]?.merged?.status}
  216. {@const message = history.messages[messageId]?.merged}
  217. <div class="w-full rounded-xl pl-5 pr-2 py-2">
  218. <Name>
  219. Merged Response
  220. {#if message.timestamp}
  221. <span
  222. class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
  223. >
  224. {dayjs(message.timestamp * 1000).format('LT')}
  225. </span>
  226. {/if}
  227. </Name>
  228. <div class="mt-1 markdown-prose w-full min-w-full">
  229. {#if (message?.content ?? '') === ''}
  230. <Skeleton />
  231. {:else}
  232. <Markdown id={`merged`} content={message.content ?? ''} />
  233. {/if}
  234. </div>
  235. </div>
  236. {/if}
  237. </div>
  238. {#if isLastMessage}
  239. <div class=" flex-shrink-0 text-gray-600 dark:text-gray-500 mt-1">
  240. <Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
  241. <button
  242. type="button"
  243. id="merge-response-button"
  244. class="{true
  245. ? 'visible'
  246. : '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"
  247. on:click={() => {
  248. mergeResponsesHandler();
  249. }}
  250. >
  251. <Merge className=" size-5 " />
  252. </button>
  253. </Tooltip>
  254. </div>
  255. {/if}
  256. </div>
  257. {/if}
  258. {/if}
  259. </div>
  260. {/if}