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
+46 -6
View File
@@ -40,7 +40,10 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
let sse: EventSource | undefined;
const textDeltaBuffer: Map<string, Map<string, string>> = new Map();
const textDeltaBuffer: Map<string, Map<string, {
content: string;
lastUpdatedAt: string;
}>> = new Map();
let flushTimeout: ReturnType<typeof setTimeout> | undefined;
let flushCbs: Set<() => void> = new Set();
@@ -54,7 +57,8 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
for (const [partId, content] of parts) {
const part = msg.parts?.find(p => p.id === partId);
if (part) {
part.content += content;
part.content += content.content;
part.lastUpdatedAt = new Date(content.lastUpdatedAt);
}
}
}
@@ -132,8 +136,8 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
textDeltaBuffer.set(payload.messageId, new Map());
}
const messageBuffer = textDeltaBuffer.get(payload.messageId)!;
const existing = messageBuffer.get(payload.partId) || '';
messageBuffer.set(payload.partId, existing + payload.content);
const existing = messageBuffer.get(payload.partId)?.content || '';
messageBuffer.set(payload.partId, { content: existing + payload.content, lastUpdatedAt: payload.lastUpdatedAt });
scheduleFlush();
break;
@@ -145,6 +149,7 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
if (part) {
nextFlush(() => {
part.content = payload.content;
part.lastUpdatedAt = payload.lastUpdatedAt;
part.finished = true;
});
}
@@ -328,7 +333,7 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
generation: null,
parentMessageId: null,
generationId: null,
focusedIndex: null,
activeChildId: null,
deleted: null,
createdAt: new Date(),
updatedAt: new Date(),
@@ -457,11 +462,46 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
);
}
const deleteMessagesFocusedDescendant = async (messageId: string): Promise<Result<void, Error>> => {
if (!data.value) return Err(new Error('No data'));
const msg = data.value.messages.find(m => m.id === messageId);
if (!msg) return Err(new Error('Message not found'));
const previousMessages = data.value!.messages;
const res = await $fetch(`/api/messages/${messageId}`, {
method: 'DELETE',
onRequest() {
if (!msg.activeChildId && (msg.parentMessageId === null || msg.parentMessageId === undefined)) {
msg.deleted = true;
return;
}
const parent = data.value!.messages.find(m => m.id === msg.parentMessageId);
data.value!.messages = data.value!.messages.filter(m => m.id !== msg.id);
if (parent?.deleted) {
data.value!.messages = data.value!.messages.filter(m => m.id !== parent.id);
}
},
onResponseError() {
data.value!.messages = previousMessages;
},
});
if (!res.ok) {
return Err(new Error('Failed to delete message'));
}
return Ok(undefined);
}
return {
topic,
sendMessage,
startGeneration,
regenerateMessage,
patchMessageLocally
patchMessageLocally,
deleteMessagesFocusedDescendant
};
}