added cors support and started chat client
This commit is contained in:
90
packages/chat-client/src/hooks/useChat.js
Normal file
90
packages/chat-client/src/hooks/useChat.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { streamMessage } from '../api/orchestration';
|
||||
|
||||
export function useChat({ activeSession, appendMessage, updateLastMessage, refreshSessions }) {
|
||||
const [streaming, setStreaming] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [lastTokenCount, setLastTokenCount] = useState(0);
|
||||
const [lastModel, setLastModel] = useState(null);
|
||||
const cancelRef = useRef(null);
|
||||
|
||||
const sendMessage = useCallback(async (text, model) => {
|
||||
if (!activeSession || !text.trim() || streaming) return;
|
||||
|
||||
setError(null);
|
||||
|
||||
// 1. Append user bubble immediately
|
||||
appendMessage({
|
||||
id: `user-${Date.now()}`,
|
||||
role: 'user',
|
||||
text,
|
||||
});
|
||||
|
||||
// 2. Append empty assistant bubble — will be filled by stream
|
||||
appendMessage({
|
||||
id: `assistant-${Date.now()}`,
|
||||
role: 'assistant',
|
||||
text: '',
|
||||
streaming: true,
|
||||
});
|
||||
|
||||
setStreaming(true);
|
||||
|
||||
// 3. Open stream
|
||||
cancelRef.current = streamMessage(
|
||||
activeSession.external_id,
|
||||
text,
|
||||
model,
|
||||
{
|
||||
onChunk: (token) => {
|
||||
updateLastMessage(msg => ({
|
||||
...msg,
|
||||
text: msg.text + token,
|
||||
}));
|
||||
},
|
||||
|
||||
onDone: ({ model: resolvedModel, tokenCount }) => {
|
||||
// Mark bubble as complete
|
||||
updateLastMessage(msg => ({ ...msg, streaming: false }));
|
||||
setLastTokenCount(tokenCount);
|
||||
setLastModel(resolvedModel);
|
||||
setStreaming(false);
|
||||
cancelRef.current = null;
|
||||
|
||||
// Refresh session list so new sessions appear in sidebar
|
||||
refreshSessions();
|
||||
},
|
||||
|
||||
onError: (err) => {
|
||||
updateLastMessage(msg => ({
|
||||
...msg,
|
||||
text: msg.text || 'Something went wrong.',
|
||||
streaming: false,
|
||||
error: true,
|
||||
}));
|
||||
setError(err.message);
|
||||
setStreaming(false);
|
||||
cancelRef.current = null;
|
||||
},
|
||||
}
|
||||
);
|
||||
}, [activeSession, streaming, appendMessage, updateLastMessage, refreshSessions]);
|
||||
|
||||
const cancelStream = useCallback(() => {
|
||||
if (cancelRef.current) {
|
||||
cancelRef.current();
|
||||
cancelRef.current = null;
|
||||
updateLastMessage(msg => ({ ...msg, streaming: false }));
|
||||
setStreaming(false);
|
||||
}
|
||||
}, [updateLastMessage]);
|
||||
|
||||
return {
|
||||
sendMessage,
|
||||
cancelStream,
|
||||
streaming,
|
||||
error,
|
||||
lastTokenCount,
|
||||
lastModel,
|
||||
};
|
||||
}
|
||||
97
packages/chat-client/src/hooks/useSession.js
Normal file
97
packages/chat-client/src/hooks/useSession.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { fetchSessions, fetchSessionHistory } from '../api/orchestration';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export function useSession() {
|
||||
const [sessions, setSessions] = useState([]);
|
||||
const [activeSession, setActiveSession] = useState(null);
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [loadingHistory, setLoadingHistory] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Load session list on mount
|
||||
useEffect(() => {
|
||||
loadSessions();
|
||||
}, []);
|
||||
|
||||
async function loadSessions() {
|
||||
try {
|
||||
const data = await fetchSessions();
|
||||
setSessions(data);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Switch to an existing session and load its history
|
||||
const selectSession = useCallback(async (session) => {
|
||||
setActiveSession(session);
|
||||
setMessages([]);
|
||||
setLoadingHistory(true);
|
||||
|
||||
try {
|
||||
const data = await fetchSessionHistory(session.external_id);
|
||||
// History comes back newest-first — reverse for display
|
||||
const history = data.episodes.reverse().map(ep => ([
|
||||
{ id: `${ep.id}-user`, role: 'user', text: ep.user_message },
|
||||
{ id: `${ep.id}-ai`, role: 'assistant', text: ep.ai_response },
|
||||
])).flat();
|
||||
|
||||
setMessages(history);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoadingHistory(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Create a new session with a generated UUID — no backend call needed yet,
|
||||
// orchestration auto-creates the session on the first message
|
||||
const createSession = useCallback(() => {
|
||||
const newSession = {
|
||||
external_id: uuidv4(),
|
||||
metadata: null,
|
||||
isNew: true, // flag so SessionList can style it differently
|
||||
};
|
||||
setSessions(prev => [newSession, ...prev]);
|
||||
setActiveSession(newSession);
|
||||
setMessages([]);
|
||||
}, []);
|
||||
|
||||
// Called by useChat after a message completes — keeps session list fresh
|
||||
const refreshSessions = useCallback(async () => {
|
||||
try {
|
||||
const data = await fetchSessions();
|
||||
setSessions(data);
|
||||
} catch {
|
||||
// non-critical — sidebar just won't update
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Append a message to the current thread (used by useChat)
|
||||
const appendMessage = useCallback((message) => {
|
||||
setMessages(prev => [...prev, message]);
|
||||
}, []);
|
||||
|
||||
// Update the last message in the thread (used by useChat during streaming)
|
||||
const updateLastMessage = useCallback((updater) => {
|
||||
setMessages(prev => {
|
||||
const updated = [...prev];
|
||||
updated[updated.length - 1] = updater(updated[updated.length - 1]);
|
||||
return updated;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
sessions,
|
||||
activeSession,
|
||||
messages,
|
||||
loadingHistory,
|
||||
error,
|
||||
selectSession,
|
||||
createSession,
|
||||
refreshSessions,
|
||||
appendMessage,
|
||||
updateLastMessage,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user