make it stream

This commit is contained in:
Douwe Osinga
2025-10-08 13:06:21 -04:00
parent 54c50dd52b
commit 49560f45e8
4 changed files with 56 additions and 12 deletions
+16 -1
View File
@@ -44,6 +44,8 @@ import {
useAgent,
} from './hooks/useAgent';
import { useNavigation } from './hooks/useNavigation';
import { USE_NEW_CHAT } from './updates';
import Pair2 from './components/Pair2';
// Route Components
const HubRouteWrapper = ({
@@ -93,7 +95,20 @@ const PairRouteWrapper = ({
const resumeSessionId = searchParams.get('resumeSessionId') ?? undefined;
return (
return USE_NEW_CHAT ? (
<Pair2
chat={chat}
setChat={setChat}
setView={setView}
agentState={agentState}
loadCurrentChat={loadCurrentChat}
setFatalError={setFatalError}
setAgentWaitingMessage={setAgentWaitingMessage}
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
resumeSessionId={resumeSessionId}
initialMessage={initialMessage}
/>
) : (
<Pair
chat={chat}
setChat={setChat}
+1
View File
@@ -184,6 +184,7 @@ function BaseChatContent({
const initialPrompt = messages.length == 0 && recipe?.prompt ? recipe.prompt : '';
return (
<div className="h-full flex flex-col min-h-0">
<h2>Warning: BaseChat2!</h2>
<MainPanelLayout
backgroundColor={'bg-background-muted'}
removeTopPadding={true}
+37 -11
View File
@@ -1,6 +1,7 @@
import { useState, useCallback, useRef } from 'react';
import { ChatState } from '../types/chatState';
import { Message } from '../api';
import { getApiUrl } from '../config';
const TextDecoder = globalThis.TextDecoder;
@@ -11,6 +12,28 @@ interface UseChatStreamProps {
onStreamFinish?: () => void;
}
function pushMessage(currentMessages: Message[], incomingMsg: Message): Message[] {
const lastMsg = currentMessages[currentMessages.length - 1];
if (lastMsg?.id && lastMsg.id === incomingMsg.id) {
const lastContent = lastMsg.content[lastMsg.content.length - 1];
const newContent = incomingMsg.content[incomingMsg.content.length - 1];
if (
lastContent?.type === 'text' &&
newContent?.type === 'text' &&
incomingMsg.content.length === 1
) {
lastContent.text += newContent.text;
} else {
lastMsg.content.push(...incomingMsg.content);
}
return [...currentMessages];
} else {
return [...currentMessages, incomingMsg];
}
}
export function useChatStream({
sessionId,
messages,
@@ -28,22 +51,24 @@ export function useChatStream({
created: Date.now(),
};
const updatedMessages = [...messages, newMessage];
setMessages(updatedMessages);
let currentMessages = [...messages, newMessage];
setMessages(currentMessages);
setChatState(ChatState.Streaming);
abortControllerRef.current = new AbortController();
try {
const response = await fetch('/reply', {
// TODO(Douwe): this side steps our API. heyapi does support streaming though which should make
// this all nice & typed
const response = await fetch(getApiUrl('/reply'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': await window.electron.getSecretKey(),
},
body: JSON.stringify({
session_id: sessionId,
messages: updatedMessages.map((m) => ({
role: m.role,
content: m.content,
})),
messages: currentMessages,
}),
signal: abortControllerRef.current.signal,
});
@@ -72,7 +97,8 @@ export function useChatStream({
if (event.message) {
const msg = event.message as Message;
setMessages([...updatedMessages, msg]);
currentMessages = pushMessage(currentMessages, msg);
setMessages(currentMessages);
}
if (event.error) {
@@ -91,8 +117,8 @@ export function useChatStream({
}
}
}
} catch (error: any) {
if (error.name !== 'AbortError') {
} catch (error) {
if (error instanceof Error && error.name !== 'AbortError') {
console.error('Stream error:', error);
}
setChatState(ChatState.Idle);
+2
View File
@@ -2,3 +2,5 @@ export const UPDATES_ENABLED = true;
export const COST_TRACKING_ENABLED = true;
export const ANNOUNCEMENTS_ENABLED = false;
export const CONFIGURATION_ENABLED = true;
export const USE_NEW_CHAT = true;