A React Native app for the ultimate thinking partner.
at main 4.2 kB view raw
1/** 2 * Message Label Utilities 3 * 4 * Computes human-readable labels for message groups based on: 5 * - Message type (assistant, tool_call, etc.) 6 * - Streaming state 7 * - Tool name (for tool calls) 8 * - Content presence (reasoning-only vs full message) 9 * 10 * Used by MessageGroupBubble to show unified "(co <action>)" labels. 11 */ 12 13import type { MessageGroup } from '../hooks/useMessageGroups'; 14 15/** 16 * Extract a specific argument from tool call arguments 17 * Expects Python-style format: tool(query="value", num=10) 18 */ 19function extractToolArgument(group: MessageGroup, argName: string): string | null { 20 const argsStr = group.toolCall?.args || group.content || ''; 21 22 // Extract from Python-style string: tool(arg="value", ...) 23 const regex = new RegExp(`${argName}=["']([^"']+)["']`); 24 const match = argsStr.match(regex); 25 if (match) { 26 return match[1]; 27 } 28 29 return null; 30} 31 32/** 33 * Tool name to human-readable action mapping 34 */ 35const TOOL_ACTIONS: Record<string, { past: string; present: string }> = { 36 web_search: { past: 'searched the web', present: 'is searching the web' }, 37 open_files: { past: 'opened files', present: 'is opening files' }, 38 memory: { past: 'recalled', present: 'is recalling' }, 39 conversation_search: { past: 'searched the conversation', present: 'is searching the conversation' }, 40 grep_files: { past: 'searched files', present: 'is searching files' }, 41 memory_replace: { past: 'updated memory', present: 'is updating memory' }, 42 memory_insert: { past: 'added to memory', present: 'is adding to memory' }, 43 fetch_webpage: { past: 'fetched a webpage', present: 'is fetching a webpage' }, 44 semantic_search_files: { past: 'searched files', present: 'is searching files' }, 45}; 46 47/** 48 * Get human-readable label for a message group 49 * 50 * Examples: 51 * - "(co said)" - assistant message with content 52 * - "(co thought)" - assistant message with only reasoning 53 * - "(co searched the web)" - completed web_search tool call 54 * - "(co is thinking)" - streaming with only reasoning 55 * - "(co is searching the web)" - streaming web_search tool call 56 */ 57export function getMessageLabel(group: MessageGroup): string { 58 const isStreaming = group.isStreaming === true; 59 60 // TOOL CALL MESSAGE 61 if (group.type === 'tool_call') { 62 const toolName = group.toolCall?.name || 'unknown_tool'; 63 64 // Special handling for memory tool with str_replace command 65 if (toolName === 'memory') { 66 // Try to detect str_replace command in the arguments 67 const args = group.toolCall?.args || group.content || ''; 68 if (args.includes('str_replace') || args.includes('command: str_replace')) { 69 return isStreaming ? '(co is updating its memory)' : '(co updated its memory)'; 70 } 71 } 72 73 // Special handling for web_search - include query 74 if (toolName === 'web_search') { 75 const query = extractToolArgument(group, 'query'); 76 if (query) { 77 return isStreaming ? `(co is searching for ${query})` : `(co searched for ${query})`; 78 } 79 } 80 81 const action = TOOL_ACTIONS[toolName]; 82 83 if (action) { 84 return isStreaming ? `(co ${action.present})` : `(co ${action.past})`; 85 } 86 87 // Fallback for unknown tools 88 return isStreaming ? `(co is using ${toolName})` : `(co used ${toolName})`; 89 } 90 91 // ASSISTANT MESSAGE 92 if (group.type === 'assistant') { 93 const hasContent = group.content && group.content.trim().length > 0; 94 const hasReasoningOnly = group.reasoning && !hasContent; 95 96 if (hasReasoningOnly) { 97 // Only reasoning, no assistant message content 98 return isStreaming ? '(co is thinking)' : '(co thought)'; 99 } 100 101 // Has assistant message content (with or without reasoning) 102 return isStreaming ? '(co is saying)' : '(co said)'; 103 } 104 105 // USER MESSAGE (shouldn't need label, but handle gracefully) 106 if (group.type === 'user') { 107 return ''; // User messages don't show labels 108 } 109 110 // COMPACTION (shouldn't need label) 111 if (group.type === 'compaction') { 112 return ''; // Compaction bars have their own styling 113 } 114 115 // ORPHANED TOOL RETURN (defensive) 116 if (group.type === 'tool_return_orphaned') { 117 return '(tool result)'; 118 } 119 120 // Fallback 121 return '(co)'; 122}