| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | import { MessageRole } from '$lib/enums';
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function filterByLeafNodeId(
|
| | messages: readonly DatabaseMessage[],
|
| | leafNodeId: string,
|
| | includeRoot: boolean = false
|
| | ): readonly DatabaseMessage[] {
|
| | const result: DatabaseMessage[] = [];
|
| | const nodeMap = new Map<string, DatabaseMessage>();
|
| |
|
| |
|
| | for (const msg of messages) {
|
| | nodeMap.set(msg.id, msg);
|
| | }
|
| |
|
| |
|
| | let startNode: DatabaseMessage | undefined = nodeMap.get(leafNodeId);
|
| | if (!startNode) {
|
| |
|
| | let latestTime = -1;
|
| | for (const msg of messages) {
|
| | if (msg.timestamp > latestTime) {
|
| | startNode = msg;
|
| | latestTime = msg.timestamp;
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | let currentNode: DatabaseMessage | undefined = startNode;
|
| | while (currentNode) {
|
| |
|
| | if (currentNode.type !== 'root' || includeRoot) {
|
| | result.push(currentNode);
|
| | }
|
| |
|
| |
|
| | if (currentNode.parent === null) {
|
| | break;
|
| | }
|
| | currentNode = nodeMap.get(currentNode.parent);
|
| | }
|
| |
|
| |
|
| | result.sort((a, b) => {
|
| | if (a.role === MessageRole.SYSTEM && b.role !== MessageRole.SYSTEM) return -1;
|
| | if (a.role !== MessageRole.SYSTEM && b.role === MessageRole.SYSTEM) return 1;
|
| |
|
| | return a.timestamp - b.timestamp;
|
| | });
|
| | return result;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function findLeafNode(messages: readonly DatabaseMessage[], messageId: string): string {
|
| | const nodeMap = new Map<string, DatabaseMessage>();
|
| |
|
| |
|
| | for (const msg of messages) {
|
| | nodeMap.set(msg.id, msg);
|
| | }
|
| |
|
| | let currentNode: DatabaseMessage | undefined = nodeMap.get(messageId);
|
| | while (currentNode && currentNode.children.length > 0) {
|
| |
|
| | const lastChildId = currentNode.children[currentNode.children.length - 1];
|
| | currentNode = nodeMap.get(lastChildId);
|
| | }
|
| |
|
| | return currentNode?.id ?? messageId;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function findDescendantMessages(
|
| | messages: readonly DatabaseMessage[],
|
| | messageId: string
|
| | ): string[] {
|
| | const nodeMap = new Map<string, DatabaseMessage>();
|
| |
|
| |
|
| | for (const msg of messages) {
|
| | nodeMap.set(msg.id, msg);
|
| | }
|
| |
|
| | const descendants: string[] = [];
|
| | const queue: string[] = [messageId];
|
| |
|
| | while (queue.length > 0) {
|
| | const currentId = queue.shift()!;
|
| | const currentNode = nodeMap.get(currentId);
|
| |
|
| | if (currentNode) {
|
| |
|
| | for (const childId of currentNode.children) {
|
| | descendants.push(childId);
|
| | queue.push(childId);
|
| | }
|
| | }
|
| | }
|
| |
|
| | return descendants;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function getMessageSiblings(
|
| | messages: readonly DatabaseMessage[],
|
| | messageId: string
|
| | ): ChatMessageSiblingInfo | null {
|
| | const nodeMap = new Map<string, DatabaseMessage>();
|
| |
|
| |
|
| | for (const msg of messages) {
|
| | nodeMap.set(msg.id, msg);
|
| | }
|
| |
|
| | const message = nodeMap.get(messageId);
|
| | if (!message) {
|
| | return null;
|
| | }
|
| |
|
| |
|
| | if (message.parent === null) {
|
| |
|
| | return {
|
| | message,
|
| | siblingIds: [messageId],
|
| | currentIndex: 0,
|
| | totalSiblings: 1
|
| | };
|
| | }
|
| |
|
| | const parentNode = nodeMap.get(message.parent);
|
| | if (!parentNode) {
|
| |
|
| | return {
|
| | message,
|
| | siblingIds: [messageId],
|
| | currentIndex: 0,
|
| | totalSiblings: 1
|
| | };
|
| | }
|
| |
|
| |
|
| | const siblingIds = parentNode.children;
|
| |
|
| |
|
| |
|
| | const siblingLeafIds = siblingIds.map((siblingId: string) => findLeafNode(messages, siblingId));
|
| |
|
| |
|
| | const currentIndex = siblingIds.indexOf(messageId);
|
| |
|
| | return {
|
| | message,
|
| | siblingIds: siblingLeafIds,
|
| | currentIndex,
|
| | totalSiblings: siblingIds.length
|
| | };
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function getMessageDisplayList(
|
| | messages: readonly DatabaseMessage[],
|
| | leafNodeId: string
|
| | ): ChatMessageSiblingInfo[] {
|
| |
|
| | const currentPath = filterByLeafNodeId(messages, leafNodeId, true);
|
| | const result: ChatMessageSiblingInfo[] = [];
|
| |
|
| |
|
| | for (const message of currentPath) {
|
| | if (message.type === 'root') {
|
| | continue;
|
| | }
|
| |
|
| | const siblingInfo = getMessageSiblings(messages, message.id);
|
| | if (siblingInfo) {
|
| | result.push(siblingInfo);
|
| | }
|
| | }
|
| |
|
| | return result;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function hasMessageSiblings(
|
| | messages: readonly DatabaseMessage[],
|
| | messageId: string
|
| | ): boolean {
|
| | const siblingInfo = getMessageSiblings(messages, messageId);
|
| | return siblingInfo ? siblingInfo.totalSiblings > 1 : false;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function getNextSibling(
|
| | messages: readonly DatabaseMessage[],
|
| | messageId: string
|
| | ): string | null {
|
| | const siblingInfo = getMessageSiblings(messages, messageId);
|
| | if (!siblingInfo || siblingInfo.currentIndex >= siblingInfo.totalSiblings - 1) {
|
| | return null;
|
| | }
|
| |
|
| | return siblingInfo.siblingIds[siblingInfo.currentIndex + 1];
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function getPreviousSibling(
|
| | messages: readonly DatabaseMessage[],
|
| | messageId: string
|
| | ): string | null {
|
| | const siblingInfo = getMessageSiblings(messages, messageId);
|
| | if (!siblingInfo || siblingInfo.currentIndex <= 0) {
|
| | return null;
|
| | }
|
| |
|
| | return siblingInfo.siblingIds[siblingInfo.currentIndex - 1];
|
| | }
|
| |
|