bug fixes, and performance improvements, and drawer

This commit is contained in:
Zoe
2026-09-27 18:44:51 -05:00
parent d48d8163a7
commit 2b9f8d64d7
52 changed files with 4913 additions and 778 deletions
+88 -16
View File
@@ -1,9 +1,86 @@
import type { AssistantContent, FilePart, ImagePart, JSONValue, ModelMessage, ToolContent } from "ai";
import { ToolCallType } from "~~/drizzle/schema";
import { Err, Ok, type Result } from "~~/types/result";
import type { Message, MessageEntity } from "~/composables/useChat";
import type { Agent } from "~/composables/useAgents";
import { resolveFileUrl } from "~~/utils/url";
import { ToolCallType } from "../drizzle/schema";
import { Err, Ok, type Result } from "../types/result";
import type { Message, MessageEntity } from "../app/composables/useChat";
import type { Agent } from "../app/composables/useAgents";
import { resolveFileUrl } from "./url";
export const getActiveMessage = <T extends { id: string; deleted?: boolean | null; activeChildId?: string | null; children: (T | undefined)[] }>(
message: T
): T | null => {
if (message.activeChildId && message.children.length > 0) {
const child = message.children.find(c => c?.id === message.activeChildId);
if (child) {
return child;
}
}
if (message.deleted === true) {
if (message.children.length > 0 && message.children[0]) {
return message.children[0];
}
return null;
}
return message;
};
export const getMessageCount = (message: {
deleted?: boolean | null;
children: unknown[];
}): number => {
if (message.deleted === true) {
return message.children.length;
}
return message.children.length + 1;
};
export const getCurrentChildIndex = (message: {
deleted?: boolean | null;
activeChildId?: string | null;
children: ({ id: string } | undefined)[];
}): number => {
if (message.deleted === true) {
if (!message.activeChildId) {
return 0;
}
const idx = message.children.findIndex(c => c?.id === message.activeChildId);
return idx >= 0 ? idx : 0;
}
if (!message.activeChildId) {
return 0;
}
const idx = message.children.findIndex(c => c?.id === message.activeChildId);
return idx >= 0 ? idx + 1 : 0;
};
export const getNextActiveChildId = (
message: {
deleted?: boolean | null;
activeChildId?: string | null;
children: ({ id: string } | undefined)[];
},
direction: -1 | 1
): string | null => {
const allItems = message.deleted === true
? message.children
: [null, ...message.children];
const currentIdx = allItems.findIndex(item => {
if (!item) {
return !message.activeChildId;
}
return item.id === message.activeChildId;
});
const validCurrentIdx = currentIdx >= 0 ? currentIdx : 0;
const newIdx = Math.max(0, Math.min(allItems.length - 1, validCurrentIdx + direction));
const newItem = allItems[newIdx];
return newItem?.id ?? null;
};
export const buildMessageTree = (flatMessages: MessageEntity[]) => {
const messagesMap = new Map(flatMessages.map(m => [m.id, { ...m, children: [] as MessageEntity[] }]));
@@ -16,26 +93,21 @@ export const buildMessageTree = (flatMessages: MessageEntity[]) => {
roots.push(msg);
}
}
return roots;
return roots.filter(root => !root.deleted || root.children.length > 0);
};
export const buildFocusedMessageTree = (messages: Readonly<Message[]>): MessageEntity[] => {
let focusedMessageTree: MessageEntity[] = [];
const focusedMessageTree: MessageEntity[] = [];
for (const message of messages) {
if (message.activeChildId && message.children.length > 0) {
const activeChild = message.children.find(c => c?.id === message.activeChildId);
if (activeChild) {
focusedMessageTree.push(activeChild);
continue;
}
const activeMessage = getActiveMessage(message);
if (activeMessage) {
focusedMessageTree.push(activeMessage);
}
focusedMessageTree.push(message);
}
return focusedMessageTree;
}
};
export interface MarshallOptions {
signFileUrl?: (fileKey: string) => string;
+21
View File
@@ -0,0 +1,21 @@
/** Stable across database Dates, JSON SSE payloads, and Nuxt hydration. */
export const serializeTopicSnapshot = (topic: unknown): string => JSON.stringify(topic, (_key, value) => {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return value;
}
return Object.fromEntries(Object.keys(value).sort().map(key => [key, value[key]]));
});
/** Absolute offsets make replay across a snapshot boundary idempotent. */
export const appendTextDelta = (current: string, content: string, offset?: number): string => {
if (offset === undefined) {
return current + content;
}
if (offset > current.length) {
// A gap requires reconciliation, not silently concatenating corrupt text.
return current;
}
const overlap = current.length - offset;
return overlap >= content.length ? current : current + content.slice(overlap);
};
+9
View File
@@ -0,0 +1,9 @@
export const provisionalTopicTitle = (message?: {
content: string;
files?: { name: string }[];
fileIds?: string[];
}): string => {
const line = message?.content.split(/\r?\n/).map(line => line.trim()).find(Boolean);
const title = line || message?.files?.[0]?.name || (message?.fileIds?.length ? "Attached files" : "New Topic");
return title.replace(/\s+/g, " ").slice(0, 100);
};