mirror of
https://git.mirrors.martin98.com/https://github.com/open-webui/open-webui
synced 2025-08-20 01:29:05 +08:00
feat: goto message
This commit is contained in:
parent
c700126c17
commit
3be626bef3
@ -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) => {
|
const showPreviousMessage = async (message) => {
|
||||||
if (message.parentId !== null) {
|
if (message.parentId !== null) {
|
||||||
let messageId =
|
let messageId =
|
||||||
@ -408,6 +449,7 @@
|
|||||||
messageId={message.id}
|
messageId={message.id}
|
||||||
idx={messageIdx}
|
idx={messageIdx}
|
||||||
{user}
|
{user}
|
||||||
|
{gotoMessage}
|
||||||
{showPreviousMessage}
|
{showPreviousMessage}
|
||||||
{showNextMessage}
|
{showNextMessage}
|
||||||
{updateChat}
|
{updateChat}
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
export let user;
|
export let user;
|
||||||
|
|
||||||
|
export let gotoMessage;
|
||||||
export let showPreviousMessage;
|
export let showPreviousMessage;
|
||||||
export let showNextMessage;
|
export let showNextMessage;
|
||||||
export let updateChat;
|
export let updateChat;
|
||||||
@ -57,6 +58,7 @@
|
|||||||
: (Object.values(history.messages)
|
: (Object.values(history.messages)
|
||||||
.filter((message) => message.parentId === null)
|
.filter((message) => message.parentId === null)
|
||||||
.map((message) => message.id) ?? [])}
|
.map((message) => message.id) ?? [])}
|
||||||
|
{gotoMessage}
|
||||||
{showPreviousMessage}
|
{showPreviousMessage}
|
||||||
{showNextMessage}
|
{showNextMessage}
|
||||||
{editMessage}
|
{editMessage}
|
||||||
@ -70,6 +72,7 @@
|
|||||||
{messageId}
|
{messageId}
|
||||||
isLastMessage={messageId === history.currentId}
|
isLastMessage={messageId === history.currentId}
|
||||||
siblings={history.messages[history.messages[messageId].parentId]?.childrenIds ?? []}
|
siblings={history.messages[history.messages[messageId].parentId]?.childrenIds ?? []}
|
||||||
|
{gotoMessage}
|
||||||
{showPreviousMessage}
|
{showPreviousMessage}
|
||||||
{showNextMessage}
|
{showNextMessage}
|
||||||
{updateChat}
|
{updateChat}
|
||||||
|
@ -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) => {
|
const showPreviousMessage = async (modelIdx) => {
|
||||||
groupedMessageIdsIdx[modelIdx] = Math.max(0, groupedMessageIdsIdx[modelIdx] - 1);
|
groupedMessageIdsIdx[modelIdx] = Math.max(0, groupedMessageIdsIdx[modelIdx] - 1);
|
||||||
|
|
||||||
@ -224,6 +253,7 @@
|
|||||||
messageId={_messageId}
|
messageId={_messageId}
|
||||||
isLastMessage={true}
|
isLastMessage={true}
|
||||||
siblings={groupedMessageIds[modelIdx].messageIds}
|
siblings={groupedMessageIds[modelIdx].messageIds}
|
||||||
|
gotoMessage={(message, messageIdx) => gotoMessage(modelIdx, messageIdx)}
|
||||||
showPreviousMessage={() => showPreviousMessage(modelIdx)}
|
showPreviousMessage={() => showPreviousMessage(modelIdx)}
|
||||||
showNextMessage={() => showNextMessage(modelIdx)}
|
showNextMessage={() => showNextMessage(modelIdx)}
|
||||||
{updateChat}
|
{updateChat}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { onMount, tick, getContext } from 'svelte';
|
import { onMount, tick, getContext } from 'svelte';
|
||||||
import type { Writable } from 'svelte/store';
|
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');
|
const i18n = getContext<Writable<i18nType>>('i18n');
|
||||||
|
|
||||||
@ -110,6 +110,7 @@
|
|||||||
|
|
||||||
export let siblings;
|
export let siblings;
|
||||||
|
|
||||||
|
export let gotoMessage: Function = () => {};
|
||||||
export let showPreviousMessage: Function;
|
export let showPreviousMessage: Function;
|
||||||
export let showNextMessage: Function;
|
export let showNextMessage: Function;
|
||||||
|
|
||||||
@ -139,6 +140,8 @@
|
|||||||
let editedContent = '';
|
let editedContent = '';
|
||||||
let editTextAreaElement: HTMLTextAreaElement;
|
let editTextAreaElement: HTMLTextAreaElement;
|
||||||
|
|
||||||
|
let messageIndexEdit = false;
|
||||||
|
|
||||||
let audioParts: Record<number, HTMLAudioElement | null> = {};
|
let audioParts: Record<number, HTMLAudioElement | null> = {};
|
||||||
let speaking = false;
|
let speaking = false;
|
||||||
let speakingIdx: number | undefined;
|
let speakingIdx: number | undefined;
|
||||||
@ -846,11 +849,50 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#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
|
<div
|
||||||
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100 min-w-fit"
|
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}
|
{siblings.indexOf(message.id) + 1}/{siblings.length}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button
|
<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"
|
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
export let siblings;
|
export let siblings;
|
||||||
|
|
||||||
|
export let gotoMessage: Function;
|
||||||
export let showPreviousMessage: Function;
|
export let showPreviousMessage: Function;
|
||||||
export let showNextMessage: Function;
|
export let showNextMessage: Function;
|
||||||
|
|
||||||
@ -38,6 +39,8 @@
|
|||||||
|
|
||||||
let showDeleteConfirm = false;
|
let showDeleteConfirm = false;
|
||||||
|
|
||||||
|
let messageIndexEdit = false;
|
||||||
|
|
||||||
let edit = false;
|
let edit = false;
|
||||||
let editedContent = '';
|
let editedContent = '';
|
||||||
let messageEditTextAreaElement: HTMLTextAreaElement;
|
let messageEditTextAreaElement: HTMLTextAreaElement;
|
||||||
@ -267,11 +270,52 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#if messageIndexEdit}
|
||||||
<div
|
<div
|
||||||
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100"
|
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}
|
{siblings.indexOf(message.id) + 1}/{siblings.length}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button
|
<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"
|
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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#if messageIndexEdit}
|
||||||
<div
|
<div
|
||||||
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100"
|
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}
|
{siblings.indexOf(message.id) + 1}/{siblings.length}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button
|
<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"
|
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user