diff --git a/packages/chat-client/src/App.jsx b/packages/chat-client/src/App.jsx index 2847d57..2e267fc 100644 --- a/packages/chat-client/src/App.jsx +++ b/packages/chat-client/src/App.jsx @@ -5,7 +5,7 @@ import InfoPanel from './components/InfoPanel'; import { useSession } from './hooks/useSession'; import { useChat } from './hooks/useChat'; -const DEFAULT_MODEL = 'companion:latest'; +import { DEFAULT_MODEL } from './config/constants'; export default function App() { const [leftOpen, setLeftOpen] = useState(true); diff --git a/packages/chat-client/src/api/orchestration.js b/packages/chat-client/src/api/orchestration.js index 10cddc2..4876111 100644 --- a/packages/chat-client/src/api/orchestration.js +++ b/packages/chat-client/src/api/orchestration.js @@ -1,14 +1,16 @@ +import { API_DEFAULTS } from "../config/constants"; + const BASE_URL = import.meta.env.VITE_ORCHESTRATION_URL ?? ''; // ── Sessions ──────────────────────────────────────────────── -export async function fetchSessions(limit = 20, offset = 0) { +export async function fetchSessions(limit = API_DEFAULTS.SESSIONS_LIMIT, offset = API_DEFAULTS.OFFSET) { const res = await fetch(`${BASE_URL}/sessions?limit=${limit}&offset=${offset}`); if (!res.ok) throw new Error(`Failed to fetch sessions: ${res.status}`); return res.json(); } -export async function fetchSessionHistory(sessionId, limit = 50, offset = 0) { +export async function fetchSessionHistory(sessionId, limit = API_DEFAULTS.HISTORY_LIMIT, offset = API_DEFAULTS.OFFSET) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}/history?limit=${limit}&offset=${offset}`); if (!res.ok) throw new Error(`Failed to fetch history: ${res.status}`); return res.json(); diff --git a/packages/chat-client/src/components/InfoPanel.jsx b/packages/chat-client/src/components/InfoPanel.jsx index 65aa63f..acf4009 100644 --- a/packages/chat-client/src/components/InfoPanel.jsx +++ b/packages/chat-client/src/components/InfoPanel.jsx @@ -1,11 +1,5 @@ import React from 'react'; - -const MODELS = [ - { value: 'companion:latest', label: 'Companion' }, - { value: 'mistral-nemo:latest', label: 'Mistral Nemo' }, - { value: 'coder:latest', label: 'Coder' }, - { value: 'qwen2.5-coder:14b', label: 'Qwen 2.5 Coder 14B' }, -]; +import { MODELS } from '../config/constants'; export default function InfoPanel({ isOpen, onToggle, activeSession, lastModel, lastTokenCount, selectedModel, onModelChange }) { diff --git a/packages/chat-client/src/config/constants.js b/packages/chat-client/src/config/constants.js new file mode 100644 index 0000000..0c76bab --- /dev/null +++ b/packages/chat-client/src/config/constants.js @@ -0,0 +1,14 @@ +export const MODELS = [ + { value: 'companion:latest', label: 'Companion' }, + { value: 'mistral-nemo:latest', label: 'Mistral Nemo' }, + { value: 'coder:latest', label: 'Coder' }, + { value: 'qwen2.5-coder:14b', label: 'Qwen 2.5 Coder 14B' }, +]; + +export const DEFAULT_MODEL = MODELS[0].value; + +export const API_DEFAULTS = { + SESSIONS_LIMIT: 20, + HISTORY_LIMIT: 50, + OFFSET: 0, +} \ No newline at end of file diff --git a/packages/chat-client/src/hooks/useSession.js b/packages/chat-client/src/hooks/useSession.js index 3070f34..bb95307 100644 --- a/packages/chat-client/src/hooks/useSession.js +++ b/packages/chat-client/src/hooks/useSession.js @@ -9,18 +9,27 @@ export function useSession() { const [loadingHistory, setLoadingHistory] = useState(false); const [error, setError] = useState(null); - // Load session list on mount - useEffect(() => { - loadSessions(); - }, []); - async function loadSessions() { + // Called by useChat after a message completes — keeps session list fresh + const refreshSessions = useCallback(async () => { try { const data = await fetchSessions(); setSessions(data); - } catch (err) { - setError(err.message); + } catch { + // non-critical — sidebar just won't update } + }, []); + + // Load session list on mount + useEffect(() => { + refreshSessions(); + }, [refreshSessions]); + + function episodesToMessages(episodes) { + return [...episodes].reverse().flatMap(ep => [ + { id: `${ep.id}-user`, role: 'user', text: ep.user_message }, + { id: `${ep.id}-ai`, role: 'assistant', text: ep.ai_response }, + ]); } // Switch to an existing session and load its history @@ -32,10 +41,7 @@ export function useSession() { 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(); + const history = episodesToMessages(data.episodes); setMessages(history); } catch (err) { @@ -58,15 +64,7 @@ export function useSession() { 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) => { diff --git a/packages/chat-client/src/index.css b/packages/chat-client/src/index.css index 9d196aa..0f46ee8 100644 --- a/packages/chat-client/src/index.css +++ b/packages/chat-client/src/index.css @@ -23,4 +23,9 @@ html, body, #root { background: var(--bg-base); color: var(--text-primary); font-size: 15px; +} + +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } } \ No newline at end of file