MultiResponseMessages.svelte 8.6 KB

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