MultiResponseMessages.svelte 9.1 KB

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