feat: goto message

This commit is contained in:
Timothy Jaeryang Baek 2025-03-29 17:48:57 -07:00
parent c700126c17
commit 3be626bef3
5 changed files with 218 additions and 16 deletions

View File

@ -107,6 +107,47 @@
}
};
const gotoMessage = async (message, idx) => {
// Determine the correct sibling list (either parent's children or root messages)
let siblings;
if (message.parentId !== null) {
siblings = history.messages[message.parentId].childrenIds;
} else {
siblings = Object.values(history.messages)
.filter((msg) => msg.parentId === null)
.map((msg) => msg.id);
}
// Clamp index to a valid range
idx = Math.max(0, Math.min(idx, siblings.length - 1));
let messageId = siblings[idx];
// If we're navigating to a different message
if (message.id !== messageId) {
// Drill down to the deepest child of that branch
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
history.currentId = messageId;
}
await tick();
// Optional auto-scroll
if ($settings?.scrollOnBranchChange ?? true) {
const element = document.getElementById('messages-container');
autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
setTimeout(() => {
scrollToBottom();
}, 100);
}
};
const showPreviousMessage = async (message) => {
if (message.parentId !== null) {
let messageId =
@ -408,6 +449,7 @@
messageId={message.id}
idx={messageIdx}
{user}
{gotoMessage}
{showPreviousMessage}
{showNextMessage}
{updateChat}

View File

@ -20,6 +20,7 @@
export let user;
export let gotoMessage;
export let showPreviousMessage;
export let showNextMessage;
export let updateChat;
@ -57,6 +58,7 @@
: (Object.values(history.messages)
.filter((message) => message.parentId === null)
.map((message) => message.id) ?? [])}
{gotoMessage}
{showPreviousMessage}
{showNextMessage}
{editMessage}
@ -70,6 +72,7 @@
{messageId}
isLastMessage={messageId === history.currentId}
siblings={history.messages[history.messages[messageId].parentId]?.childrenIds ?? []}
{gotoMessage}
{showPreviousMessage}
{showNextMessage}
{updateChat}

View File

@ -58,6 +58,35 @@
}
}
const gotoMessage = async (modelIdx, messageIdx) => {
// Clamp messageIdx to ensure it's within valid range
groupedMessageIdsIdx[modelIdx] = Math.max(
0,
Math.min(messageIdx, groupedMessageIds[modelIdx].messageIds.length - 1)
);
// Get the messageId at the specified index
let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]];
console.log(messageId);
// Traverse the branch to find the deepest child message
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
// Update the current message ID in history
history.currentId = messageId;
// Await UI updates
await tick();
await updateChat();
// Trigger scrolling after navigation
triggerScroll();
};
const showPreviousMessage = async (modelIdx) => {
groupedMessageIdsIdx[modelIdx] = Math.max(0, groupedMessageIdsIdx[modelIdx] - 1);
@ -224,6 +253,7 @@
messageId={_messageId}
isLastMessage={true}
siblings={groupedMessageIds[modelIdx].messageIds}
gotoMessage={(message, messageIdx) => gotoMessage(modelIdx, messageIdx)}
showPreviousMessage={() => showPreviousMessage(modelIdx)}
showNextMessage={() => showNextMessage(modelIdx)}
{updateChat}

View File

@ -5,7 +5,7 @@
import { createEventDispatcher } from 'svelte';
import { onMount, tick, getContext } from 'svelte';
import type { Writable } from 'svelte/store';
import type { i18n as i18nType } from 'i18next';
import type { i18n as i18nType, t } from 'i18next';
const i18n = getContext<Writable<i18nType>>('i18n');
@ -110,6 +110,7 @@
export let siblings;
export let gotoMessage: Function = () => {};
export let showPreviousMessage: Function;
export let showNextMessage: Function;
@ -139,6 +140,8 @@
let editedContent = '';
let editTextAreaElement: HTMLTextAreaElement;
let messageIndexEdit = false;
let audioParts: Record<number, HTMLAudioElement | null> = {};
let speaking = false;
let speakingIdx: number | undefined;
@ -846,11 +849,50 @@
</svg>
</button>
<div
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100 min-w-fit"
>
{siblings.indexOf(message.id) + 1}/{siblings.length}
</div>
{#if messageIndexEdit}
<div
class="text-sm flex justify-center font-semibold self-center dark:text-gray-100 min-w-fit"
>
<input
id="message-index-input-{message.id}"
type="number"
value={siblings.indexOf(message.id) + 1}
min="1"
max={siblings.length}
on:focus={(e) => {
e.target.select();
}}
on:blur={(e) => {
gotoMessage(message, e.target.value - 1);
messageIndexEdit = false;
}}
on:keydown={(e) => {
if (e.key === 'Enter') {
gotoMessage(message, e.target.value - 1);
messageIndexEdit = false;
}
}}
class="bg-transparent font-semibold self-center dark:text-gray-100 min-w-fit outline-hidden"
/>/{siblings.length}
</div>
{:else}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100 min-w-fit"
on:dblclick={async () => {
messageIndexEdit = true;
await tick();
const input = document.getElementById(`message-index-input-${message.id}`);
if (input) {
input.focus();
input.select();
}
}}
>
{siblings.indexOf(message.id) + 1}/{siblings.length}
</div>
{/if}
<button
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"

View File

@ -27,6 +27,7 @@
export let siblings;
export let gotoMessage: Function;
export let showPreviousMessage: Function;
export let showNextMessage: Function;
@ -38,6 +39,8 @@
let showDeleteConfirm = false;
let messageIndexEdit = false;
let edit = false;
let editedContent = '';
let messageEditTextAreaElement: HTMLTextAreaElement;
@ -267,11 +270,52 @@
</svg>
</button>
<div
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100"
>
{siblings.indexOf(message.id) + 1}/{siblings.length}
</div>
{#if messageIndexEdit}
<div
class="text-sm flex justify-center font-semibold self-center dark:text-gray-100 min-w-fit"
>
<input
id="message-index-input-{message.id}"
type="number"
value={siblings.indexOf(message.id) + 1}
min="1"
max={siblings.length}
on:focus={(e) => {
e.target.select();
}}
on:blur={(e) => {
gotoMessage(message, e.target.value - 1);
messageIndexEdit = false;
}}
on:keydown={(e) => {
if (e.key === 'Enter') {
gotoMessage(message, e.target.value - 1);
messageIndexEdit = false;
}
}}
class="bg-transparent font-semibold self-center dark:text-gray-100 min-w-fit outline-hidden"
/>/{siblings.length}
</div>
{:else}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100 min-w-fit"
on:dblclick={async () => {
messageIndexEdit = true;
await tick();
const input = document.getElementById(
`message-index-input-${message.id}`
);
if (input) {
input.focus();
input.select();
}
}}
>
{siblings.indexOf(message.id) + 1}/{siblings.length}
</div>
{/if}
<button
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
@ -398,11 +442,52 @@
</svg>
</button>
<div
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100"
>
{siblings.indexOf(message.id) + 1}/{siblings.length}
</div>
{#if messageIndexEdit}
<div
class="text-sm flex justify-center font-semibold self-center dark:text-gray-100 min-w-fit"
>
<input
id="message-index-input-{message.id}"
type="number"
value={siblings.indexOf(message.id) + 1}
min="1"
max={siblings.length}
on:focus={(e) => {
e.target.select();
}}
on:blur={(e) => {
gotoMessage(message, e.target.value - 1);
messageIndexEdit = false;
}}
on:keydown={(e) => {
if (e.key === 'Enter') {
gotoMessage(message, e.target.value - 1);
messageIndexEdit = false;
}
}}
class="bg-transparent font-semibold self-center dark:text-gray-100 min-w-fit outline-hidden"
/>/{siblings.length}
</div>
{:else}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100 min-w-fit"
on:dblclick={async () => {
messageIndexEdit = true;
await tick();
const input = document.getElementById(
`message-index-input-${message.id}`
);
if (input) {
input.focus();
input.select();
}
}}
>
{siblings.indexOf(message.id) + 1}/{siblings.length}
</div>
{/if}
<button
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"