char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
0afb805b1
char/aliceai-t5-moe
1/** 2* Message branching utilities for conversation tree navigation. 3* 4* Conversation branching allows users to edit messages and create alternate paths 5* while preserving the original conversation flow. Each message has parent/children 6* relationships forming a tree structure. 7* 8* Example tree: 9* root 10* ├── message 1 (user) 11* │ └── message 2 (assistant) 12* │ ├── message 3 (user) 13* │ └── message 6 (user) ← new branch 14* └── message 4 (user) 15* └── message 5 (assistant) 16*/ 17 18import { MessageRole } from '$lib/enums' ; 19 20/** 21* Finds a message by its ID in the given messages array. 22*/ 23export function findMessageById ( 24messages :readonly DatabaseMessage [], 25id :string | null | undefined 26) :DatabaseMessage | undefined { 27if ( ! id ) return undefined ; 28 29return messages . find (( m ) => m . id === id ); 30} 31 32/** 33* Filters messages to get the conversation path from root to a specific leaf node. 34* If the leafNodeId doesn't exist, returns the path with the latest timestamp. 35* 36* @param messages - All messages in the conversation 37* @param leafNodeId - The target leaf node ID to trace back from 38* @param includeRoot - Whether to include root messages in the result 39* @returns Array of messages from root to leaf, sorted by timestamp 40*/ 41export function filterByLeafNodeId ( 42messages :readonly DatabaseMessage [], 43leafNodeId :string , 44includeRoot :boolean = false 45) :readonly DatabaseMessage [] { 46const result :DatabaseMessage [] = []; 47const nodeMap = new Map < string , DatabaseMessage >(); 48 49// Build node map for quick lookups 50for ( const msg of messages ) { 51nodeMap . set ( msg . id , msg ); 52} 53 54// Find the starting node (leaf node or latest if not found) 55let startNode :DatabaseMessage | undefined = nodeMap . get ( leafNodeId ); 56 57if ( ! startNode ) { 58// If leaf node not found, use the message with latest timestamp 59let latestTime = - 1 ; 60 61for ( const msg of messages ) { 62if ( msg . timestamp > latestTime ) { 63startNode = msg ; 64latestTime = msg . timestamp ; 65} 66} 67} 68 69// Traverse from leaf to root, collecting messages 70let currentNode :DatabaseMessage | undefined = startNode ; 71 72while ( currentNode ) { 73// Include message if it's not root, or if we want to include root 74if ( currentNode . type !== 'root' || includeRoot ) { 75result . push ( currentNode ); 76} 77 78// Stop traversal if parent is null (reached root) 79if ( currentNode . parent === null ) { 80break ; 81} 82 83currentNode = nodeMap . get ( currentNode . parent ); 84} 85 86// Sort: system messages first, then by timestamp 87result . sort (( a , b ) => { 88if ( a . role === MessageRole . SYSTEM && b . role !== MessageRole . SYSTEM ) return - 1 ; 89 90if ( a . role !== MessageRole . SYSTEM && b . role === MessageRole . SYSTEM ) return 1 ; 91 92return a . timestamp - b . timestamp ; 93}); 94 95return result ; 96} 97 98/** 99* Finds the leaf node (message with no children) for a given message branch. 100* Traverses down the tree following the last child until reaching a leaf. 101* 102* @param nodeMap - Map of messages keyed by ID 103* @param messageId - Starting message ID to find leaf for 104* @returns The leaf node ID, or the original messageId if no children 105*/ 106function findLeafNodeInMap ( 107nodeMap :ReadonlyMap < string , DatabaseMessage >, 108messageId :string , 109leafCache ?:Map < string , string > 110) :string { 111const path :string [] = []; 112 113let currentNode :DatabaseMessage | undefined = nodeMap . get ( messageId ); 114 115while ( currentNode && currentNode . children . length > 0 ) { 116// Follow the last child (most recent branch) 117const cached = leafCache ?. get ( currentNode . id ); 118 119if ( cached !== undefined ) { 120for ( const id of path ) leafCache ?. set ( id , cached ); 121 122return cached ; 123} 124 125path . push ( currentNode . id ); 126const lastChildId = currentNode . children [ currentNode . children . length - 1 ]; 127 128currentNode = nodeMap . get ( lastChildId ); 129} 130 131const leafId = currentNode ?. id ?? messageId ; 132 133for ( const id of path ) leafCache ?. set ( id , leafId ); 134 135return leafId ; 136} 137 138/** 139* Convenience wrapper around {@link findLeafNodeInMap} for callers that have a flat message array. 140*/ 141export function findLeafNode ( messages :readonly DatabaseMessage [], messageId :string ) :string { 142const nodeMap = new Map ( messages . map (( msg ) => [ msg . id , msg ] as const )); 143 144return findLeafNodeInMap ( nodeMap , messageId ); 145} 146 147/** 148* Finds all descendant messages (children, grandchildren, etc.) of a given message. 149* This is used for cascading deletion to remove all messages in a branch. 150* 151* @param messages - All messages in the conversation 152* @param messageId - The root message ID to find descendants for 153* @returns Array of all descendant message IDs 154*/ 155export function findDescendantMessages ( 156messages :readonly DatabaseMessage [], 157messageId :string 158) :string [] { 159const nodeMap = new Map < string , DatabaseMessage >(); 160 161// Build node map for quick lookups 162for ( const msg of messages ) { 163nodeMap . set ( msg . id , msg ); 164} 165 166const descendants :string [] = []; 167const queue :string [] = [ messageId ]; 168 169while ( queue . length > 0 ) { 170const currentId = queue . shift () ! ; 171const currentNode = nodeMap . get ( currentId ); 172 173if ( currentNode ) { 174// Add all children to the queue and descendants list 175for ( const childId of currentNode . children ) { 176descendants . push ( childId ); 177queue . push ( childId ); 178} 179} 180} 181 182return descendants ; 183} 184 185/** 186* Gets sibling information for a message, including all sibling IDs and current position. 187* Siblings are messages that share the same parent. 188* 189* @param nodeMap - Map of messages keyed by ID 190* @param messageId - The message to get sibling info for 191* @returns Sibling information including leaf node IDs for navigation 192*/ 193export function getMessageSiblings ( 194nodeMap :ReadonlyMap < string , DatabaseMessage >, 195messageId :string , 196leafCache ?:Map < string , string > 197) :ChatMessageSiblingInfo | null { 198const message = nodeMap . get ( messageId ); 199 200if ( ! message ) { 201return null ; 202} 203 204// Handle null parent (root message) case 205if ( message . parent === null ) { 206// No parent means this is likely a root node with no siblings 207return { 208currentIndex :0 , 209 message, 210siblingIds :[ messageId ], 211totalSiblings :1 212}; 213} 214 215const parentNode = nodeMap . get ( message . parent ); 216 217if ( ! parentNode ) { 218// Parent not found - treat as single message 219return { 220currentIndex :0 , 221 message, 222siblingIds :[ messageId ], 223totalSiblings :1 224}; 225} 226 227// Get all sibling IDs (including self) 228const siblingIds = parentNode . children ; 229// Convert sibling message IDs to their corresponding leaf node IDs 230// This allows navigation between different conversation branches 231const siblingLeafIds = siblingIds . map (( siblingId :string ) => 232findLeafNodeInMap ( nodeMap , siblingId , leafCache ) 233); 234// Find current message's position among siblings 235const currentIndex = siblingIds . indexOf ( messageId ); 236 237return { 238 currentIndex, 239 message, 240siblingIds :siblingLeafIds , 241totalSiblings :siblingIds . length 242}; 243} 244 245/** 246* Builds sibling information for every message in a conversation. 247* 248* @param messages - All messages in the conversation 249* @returns Map of message ID to its sibling information 250*/ 251export function buildSiblingInfoMap ( 252messages :readonly DatabaseMessage [] 253) :Map < string , ChatMessageSiblingInfo > { 254const nodeMap = new Map ( messages . map (( msg ) => [ msg . id , msg ] as const )); 255const siblingMap = new Map < string , ChatMessageSiblingInfo >(); 256// Leaf walks repeat along the same child chains for every message; memoize 257// them per build so each edge is walked once instead of O(messages^2) 258const leafCache = new Map < string , string >(); 259 260for ( const msg of messages ) { 261const info = getMessageSiblings ( nodeMap , msg . id , leafCache ); 262 263if ( info ) { 264siblingMap . set ( msg . id , info ); 265} 266} 267 268return siblingMap ; 269}