refactor: replace focusedIndex with activeChildId

- Replace focusedIndex integer with activeChildId string in messages schema
- Add migration to convert existing data and drop focused_index column
- Add  hooks for providers.updatedAt and messageParts.lastUpdatedAt
- Add file size column to files table
- Update message navigation to use child ID-based lookup
- Simplify delete logic by removing focusedIndex math
- Fix file delete endpoint filename ([id[ -> [id])
This commit is contained in:
Zoe
2026-05-11 17:41:57 -05:00
parent 7a944afdb5
commit 103dedd9cc
16 changed files with 2106 additions and 1793 deletions
+1 -2
View File
@@ -30,7 +30,6 @@ const handleSubmit = async (message: BaseMessage, model: ModelWithProvider | nul
}
console.log("handleSubmit", message);
if (!message.content) return;
pendingMessage.value = {
id: '',
@@ -43,7 +42,7 @@ const handleSubmit = async (message: BaseMessage, model: ModelWithProvider | nul
parentMessageId: null,
children: [],
generationId: null,
focusedIndex: null,
activeChildId: null,
attachments: [],
deleted: false,
createdAt: new Date(),
+66 -21
View File
@@ -16,7 +16,7 @@ const { addShortcut } = useKeyboardShortcuts();
const agent = getAgent(route.params.id as string);
const topicId = computed(() => route.params.topicId as string);
const { topic, sendMessage, startGeneration, regenerateMessage, patchMessageLocally } = await useChat(topicId);
const { topic, sendMessage, startGeneration, regenerateMessage, patchMessageLocally, deleteMessagesFocusedDescendant } = await useChat(topicId);
watch(() => topic.value?.name, (newTopicName) => {
if (newTopicName !== undefined) {
@@ -61,12 +61,8 @@ const handleRegenerate = async (message: Message) => {
}
let messageId: string;
if (
(message.focusedIndex !== undefined && message.focusedIndex !== null)
&& message.focusedIndex > 0
&& message.children.length > 0
) {
messageId = message.children[message.focusedIndex - 1]!.id;
if (message.activeChildId && message.children.length > 0) {
messageId = message.activeChildId;
} else {
messageId = message.id;
}
@@ -98,9 +94,18 @@ const flatMessages = computed(() => {
});
const handleDelete = async (rootMessage: Message) => {
await $fetch(`/api/messages/${rootMessage.id}`, {
method: 'DELETE',
});
let messageId: string;
if (rootMessage.activeChildId && rootMessage.children.length > 0) {
messageId = rootMessage.activeChildId;
} else {
messageId = rootMessage.id;
}
const result = await deleteMessagesFocusedDescendant(messageId);
if (!result.ok) {
console.error('Failed to delete message:', result.error);
return;
}
if (flatMessages.value.length === 0) {
await navigateTo(`/agent/${route.params.id}`);
@@ -108,6 +113,20 @@ const handleDelete = async (rootMessage: Message) => {
}
}
const handleResize = () => {
const el = chatPaneWrapper.value;
if (!el) return;
const atBottom = (el.scrollHeight - el.scrollTop - el.clientHeight) <= 80;
if (!atBottom) return;
nextTick().then(() => {
requestAnimationFrame(() => {
scrollToBottom('instant');
});
});
}
const activeGeneration = computed(() => {
if (topic.value === null) return null;
const generations = flatMessages.value.flatMap(message => message.generation);
@@ -155,8 +174,11 @@ addShortcut(['alt', '['], async (event) => {
const lastMessage = topic.value?.messages?.at(-1);
if (lastMessage && lastMessage.children.length > 0) {
const newIndex = Math.max(0, (lastMessage.focusedIndex || 0) - 1);
patchMessageLocally(lastMessage.id, { focusedIndex: newIndex });
const currentIdx = lastMessage.children.findIndex(c => c?.id === lastMessage.activeChildId);
const prevIdx = Math.max(0, currentIdx);
if (currentIdx > 0) {
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[prevIdx - 1]!.id });
}
}
})
@@ -165,10 +187,20 @@ addShortcut(['alt', ']'], async (event) => {
event.stopPropagation();
const lastMessage = topic.value?.messages?.at(-1);
const messageCount = lastMessage ? (lastMessage.deleted ? lastMessage.children.length : lastMessage.children.length + 1) : 0;
if (lastMessage && lastMessage.children.length > 0) {
const newIndex = Math.min(messageCount - 1, (lastMessage.focusedIndex || 0) + 1);
patchMessageLocally(lastMessage.id, { focusedIndex: newIndex });
const currentIdx = lastMessage.children.findIndex(c => c?.id === lastMessage.activeChildId);
const maxIdx = lastMessage.deleted ? lastMessage.children.length - 1 : lastMessage.children.length;
const nextIdx = Math.min(maxIdx, currentIdx + 1);
if (lastMessage.deleted) {
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[nextIdx]?.id ?? null });
} else {
if (nextIdx === 0) {
patchMessageLocally(lastMessage.id, { activeChildId: null });
} else {
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[nextIdx - 1]?.id ?? null });
}
}
}
})
@@ -178,20 +210,32 @@ addShortcut(['ctrl', 'alt', 'arrowleft'], async (event) => {
event.preventDefault();
event.stopPropagation();
const newIndex = Math.max(0, (lastMessage.focusedIndex || 0) - 1);
patchMessageLocally(lastMessage.id, { focusedIndex: newIndex });
const currentIdx = lastMessage.children.findIndex(c => c?.id === lastMessage.activeChildId);
if (currentIdx > 0) {
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[currentIdx - 1]!.id });
}
}
})
addShortcut(['ctrl', 'alt', 'arrowright'], async (event) => {
const lastMessage = topic.value?.messages?.at(-1);
const messageCount = lastMessage ? (lastMessage.deleted ? lastMessage.children.length : lastMessage.children.length + 1) : 0;
if (lastMessage && lastMessage.children.length > 0) {
event.preventDefault();
event.stopPropagation();
const newIndex = Math.min(messageCount - 1, (lastMessage.focusedIndex || 0) + 1);
patchMessageLocally(lastMessage.id, { focusedIndex: newIndex });
const currentIdx = lastMessage.children.findIndex(c => c?.id === lastMessage.activeChildId);
const maxIdx = lastMessage.deleted ? lastMessage.children.length - 1 : lastMessage.children.length;
const nextIdx = Math.min(maxIdx, currentIdx + 1);
if (lastMessage.deleted) {
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[nextIdx]?.id ?? null });
} else {
if (nextIdx === 0) {
patchMessageLocally(lastMessage.id, { activeChildId: null });
} else {
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[nextIdx - 1]?.id ?? null });
}
}
}
})
@@ -249,7 +293,8 @@ console.log("full page render took", Date.now() - rootStart);
<div class="sticky bottom-0 z-10 bg-[var(--bg-surface)] pb-4 w-full rounded-t-2xl">
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
:loading="activeGeneration !== null" :agent="agent"
:providers="providers?.filter(p => p.enabled)" @submit="submitMessage" @cancel="handleCancel" />
:providers="providers?.filter(p => p.enabled)" @submit="submitMessage" @cancel="handleCancel"
@resize="handleResize" />
</div>
</div>
</div>