()
| 96 | } |
| 97 | |
| 98 | const handleSend = async () => { |
| 99 | const query = input.trim(); |
| 100 | if (!query || loading) return; |
| 101 | |
| 102 | setInput(''); |
| 103 | setLoading(true); |
| 104 | setIsNewChat(false); |
| 105 | setIsStreaming(true); |
| 106 | setStreamingSteps([]); |
| 107 | setStreamingContent(''); |
| 108 | |
| 109 | // Add user message optimistically |
| 110 | const userMsg = { role: 'user', content: query, offset: messageOffset }; |
| 111 | setMessages((prev) => [...prev, userMsg]); |
| 112 | const currentOffset = messageOffset; |
| 113 | setMessageOffset((prev) => prev + 1); |
| 114 | |
| 115 | // Accumulate tokens so we can fall back if onDone never fires |
| 116 | let tokenAccumulator = ''; |
| 117 | let streamedDocuments = null; |
| 118 | let doneHandled = false; |
| 119 | let doneData = null; |
| 120 | |
| 121 | try { |
| 122 | await sendMessageStream( |
| 123 | sessionId, |
| 124 | query, |
| 125 | history, |
| 126 | documents, |
| 127 | selectedDatasets, |
| 128 | currentOffset, |
| 129 | { |
| 130 | onStep: (step) => { |
| 131 | setStreamingSteps((prev) => [...prev, step]); |
| 132 | }, |
| 133 | onToken: (token) => { |
| 134 | tokenAccumulator += token; |
| 135 | setStreamingContent(tokenAccumulator); |
| 136 | }, |
| 137 | onDocuments: (docs) => { |
| 138 | streamedDocuments = docs; |
| 139 | }, |
| 140 | onDone: (data) => { |
| 141 | doneHandled = true; |
| 142 | doneData = data; |
| 143 | }, |
| 144 | onError: (error) => { |
| 145 | setMessages((prev) => [ |
| 146 | ...prev, |
| 147 | { role: 'assistant', content: `Something went wrong: ${error}` }, |
| 148 | ]); |
| 149 | doneHandled = true; // prevent fallback from also adding a message |
| 150 | }, |
| 151 | } |
| 152 | ); |
| 153 | } catch (err) { |
| 154 | setMessages((prev) => [ |
| 155 | ...prev, |
no test coverage detected