A React Native app for the ultimate thinking partner.
1/**
2 * useMessageInteractions Hook
3 *
4 * Manages state and handlers for message interactions:
5 * - Expanding/collapsing reasoning blocks
6 * - Expanding/collapsing compaction summaries
7 * - Expanding/collapsing orphaned tool returns
8 * - Copying message content to clipboard
9 *
10 * Uses Set data structure for O(1) lookups and efficient state management.
11 */
12
13import { useState, useCallback } from 'react';
14import * as Clipboard from 'expo-clipboard';
15
16export function useMessageInteractions() {
17 // State - using Sets for efficient O(1) lookups
18 const [expandedReasoning, setExpandedReasoning] = useState<Set<string>>(new Set());
19 const [expandedCompaction, setExpandedCompaction] = useState<Set<string>>(new Set());
20 const [expandedToolReturns, setExpandedToolReturns] = useState<Set<string>>(new Set());
21 const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null);
22
23 // Toggle reasoning expansion for a message
24 const toggleReasoning = useCallback((messageId: string) => {
25 setExpandedReasoning((prev) => {
26 const next = new Set(prev);
27 if (next.has(messageId)) {
28 next.delete(messageId);
29 } else {
30 next.add(messageId);
31 }
32 return next;
33 });
34 }, []);
35
36 // Toggle compaction expansion for a message
37 const toggleCompaction = useCallback((messageId: string) => {
38 setExpandedCompaction((prev) => {
39 const next = new Set(prev);
40 if (next.has(messageId)) {
41 next.delete(messageId);
42 } else {
43 next.add(messageId);
44 }
45 return next;
46 });
47 }, []);
48
49 // Toggle tool return expansion for a message
50 const toggleToolReturn = useCallback((messageId: string) => {
51 setExpandedToolReturns((prev) => {
52 const next = new Set(prev);
53 if (next.has(messageId)) {
54 next.delete(messageId);
55 } else {
56 next.add(messageId);
57 }
58 return next;
59 });
60 }, []);
61
62 // Copy message content to clipboard with 2-second confirmation
63 const copyToClipboard = useCallback(async (content: string, messageId?: string) => {
64 try {
65 await Clipboard.setStringAsync(content);
66 if (messageId) {
67 setCopiedMessageId(messageId);
68 setTimeout(() => setCopiedMessageId(null), 2000);
69 }
70 } catch (error) {
71 console.error('Failed to copy to clipboard:', error);
72 }
73 }, []);
74
75 // Auto-expand reasoning for a message (doesn't toggle, just adds)
76 const expandReasoning = useCallback((messageId: string) => {
77 setExpandedReasoning((prev) => {
78 if (prev.has(messageId)) return prev; // Already expanded
79 const next = new Set(prev);
80 next.add(messageId);
81 return next;
82 });
83 }, []);
84
85 return {
86 // State
87 expandedReasoning,
88 expandedCompaction,
89 expandedToolReturns,
90 copiedMessageId,
91
92 // Handlers
93 toggleReasoning,
94 toggleCompaction,
95 toggleToolReturn,
96 copyToClipboard,
97 expandReasoning,
98 };
99}