MultiResponseMessages.svelte 10 KB

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