From 7a43beb3750e35a3c5707f1cea2a180d599f8ab3 Mon Sep 17 00:00:00 2001 From: AIinWEB3 Date: Thu, 6 Aug 2026 00:59:32 -0700 Subject: [PATCH 1/8] =?UTF-8?q?style(design):=20FINDING-001=20=E2=80=94=20?= =?UTF-8?q?improve=20waiting=20and=20streaming=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demos/02-lumacare/src/App.tsx | 82 ++++++++++++++++--- demos/02-lumacare/src/guide.ts | 7 ++ .../tests/guide-formatting.test.ts | 12 +++ 3 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 demos/02-lumacare/src/guide.ts create mode 100644 demos/02-lumacare/tests/guide-formatting.test.ts diff --git a/demos/02-lumacare/src/App.tsx b/demos/02-lumacare/src/App.tsx index f03995b..764e9a7 100644 --- a/demos/02-lumacare/src/App.tsx +++ b/demos/02-lumacare/src/App.tsx @@ -5,6 +5,7 @@ import { Phone, RefreshCw, ShieldCheck, Sparkles, X, } from 'lucide-react'; import { api, apiHeaders, SSEParser, type StreamEvent } from './api'; +import { normalizeGuideText } from './guide'; import type { AgentFile, SessionRecord } from './types'; type Config = { user: { id: string; name: string; initials: string }; users: { id: string; name: string; initials: string }[]; agent: { name: string; model: string } }; @@ -30,12 +31,13 @@ function extractTurnText(payload: any): string { } function Guide({ text }: { text: string }) { - return
{text.split('\n').map((line, index) => { + const displayText = normalizeGuideText(text); + return
{displayText.split('\n').map((line, index) => { const clean = line.trim(); if (!clean) return
; if (clean.startsWith('## ')) return

{clean.slice(3)}

; if (clean.startsWith('# ')) return

{clean.slice(2)}

; - if (/^[-*] /.test(clean)) return
{clean.slice(2).replace(/\*\*/g, '')}
; + if (/^[-*] /.test(clean)) return
{clean.slice(2).replace(/^\[[ xX]\]\s*/, '').replace(/\*\*/g, '')}
; if (/^\d+\. /.test(clean)) return
{clean.match(/^\d+/)?.[0]}{clean.replace(/^\d+\. /, '').replace(/\*\*/g, '')}
; return

{clean.replace(/\*\*/g, '')}

; })}
; @@ -53,7 +55,37 @@ export default function App() { const [error, setError] = useState(''); const [historyOpen, setHistoryOpen] = useState(false); const [mobileNav, setMobileNav] = useState(false); + const [loadingSession, setLoadingSession] = useState(false); + const [elapsedSeconds, setElapsedSeconds] = useState(0); + const [isFollowing, setIsFollowing] = useState(true); const composerRef = useRef(null); + const resultRef = useRef(null); + const resultBodyRef = useRef(null); + const activeSessionRef = useRef(null); + const abortRef = useRef(null); + const streamBufferRef = useRef(''); + const frameRef = useRef(null); + + useEffect(() => { activeSessionRef.current = activeSession; }, [activeSession]); + + useEffect(() => { + if (status !== 'running') { setElapsedSeconds(0); return; } + const startedAt = Date.now(); + const timer = window.setInterval(() => setElapsedSeconds(Math.floor((Date.now() - startedAt) / 1000)), 1000); + return () => window.clearInterval(timer); + }, [status]); + + useEffect(() => { + if (status !== 'running') return; + const mobile = window.matchMedia('(max-width: 850px)').matches; + window.setTimeout(() => resultRef.current?.scrollIntoView({ behavior: mobile ? 'smooth' : 'auto', block: 'start' }), 80); + }, [status]); + + useEffect(() => { + const body = resultBodyRef.current; + if (!body || status !== 'running' || !isFollowing) return; + body.scrollTop = body.scrollHeight; + }, [answer, status, isFollowing]); const loadSessions = useCallback(async () => { const result = await api<{ sessions: SessionRecord[] }>('/api/sessions', userId); @@ -77,29 +109,47 @@ export default function App() { }, [loadSessions, userId]); const handleEvent = useCallback((event: StreamEvent, prompt: string) => { - if (event.type === 'response.created') setActiveSession({ sessionId: event.response?.metadata?.session_id, responseId: event.response?.id, userId, title: prompt.length > 64 ? `${prompt.slice(0, 64)}…` : prompt, prompt, status: 'running', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); - else if (event.type === 'response.output_text.delta') setAnswer((value) => value + String(event.delta || '')); - else if (event.type === 'response.completed' || event.type === 'response.incomplete' || event.type === 'response.failed') setStatus(event.type === 'response.completed' ? 'completed' : event.type === 'response.incomplete' ? 'incomplete' : event.response?.status === 'cancelled' ? 'cancelled' : 'failed'); + if (event.type === 'response.created') { + const session = { sessionId: event.response?.metadata?.session_id, responseId: event.response?.id, userId, title: prompt.length > 64 ? `${prompt.slice(0, 64)}…` : prompt, prompt, status: 'running', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; + activeSessionRef.current = session; setActiveSession(session); + } else if (event.type === 'response.output_text.delta') { + streamBufferRef.current += String(event.delta || ''); + if (frameRef.current === null) frameRef.current = requestAnimationFrame(() => { + const delta = streamBufferRef.current; streamBufferRef.current = ''; frameRef.current = null; + setAnswer((value) => value + delta); + }); + } else if (event.type === 'response.completed' || event.type === 'response.incomplete' || event.type === 'response.failed') { + if (frameRef.current !== null) cancelAnimationFrame(frameRef.current); + const delta = streamBufferRef.current; streamBufferRef.current = ''; frameRef.current = null; + if (delta) setAnswer((value) => value + delta); + setStatus(event.type === 'response.completed' ? 'completed' : event.type === 'response.incomplete' ? 'incomplete' : event.response?.status === 'cancelled' ? 'cancelled' : 'failed'); + } }, [userId]); const runTask = async (continuation?: string) => { const prompt = (continuation || input).trim(); if (!prompt || status === 'running') return; - setError(''); setAnswer(''); setFiles([]); setStatus('running'); if (!continuation) setInput(''); + setError(''); setAnswer(''); setFiles([]); setIsFollowing(true); setStatus('running'); if (!continuation) setInput(''); + const controller = new AbortController(); abortRef.current = controller; try { const body: Record = { featureKey: 'care_companion', input: prompt }; if (activeSession && continuation) { body.sessionId = activeSession.sessionId; body.previousResponseId = activeSession.responseId; } - const response = await fetch('/api/runs', { method: 'POST', headers: apiHeaders(userId, true), body: JSON.stringify(body) }); + const response = await fetch('/api/runs', { method: 'POST', headers: apiHeaders(userId, true), body: JSON.stringify(body), signal: controller.signal }); if (!response.ok || !response.body) { const data = await response.json().catch(() => ({})); throw new Error(data.error || 'LumaCare could not start your guide.'); } const parser = new SSEParser(); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; parser.push(decoder.decode(value, { stream: true })).forEach((event) => handleEvent(event, prompt)); } parser.flush().forEach((event) => handleEvent(event, prompt)); setTimeout(() => setActiveSession((current) => { if (current) refreshSession(current).catch(() => {}); return current; }), 700); - } catch (e) { setStatus('failed'); setError((e as Error).message); } + } catch (e) { if ((e as Error).name !== 'AbortError') { setStatus('failed'); setError((e as Error).message); } } + finally { abortRef.current = null; } }; - const openSession = async (session: SessionRecord) => { setHistoryOpen(false); setAnswer(''); setStatus('running'); try { await refreshSession(session); } catch (e) { setError((e as Error).message); setStatus('failed'); } }; - const cancelRun = async () => { if (!activeSession) return; await api(`/api/sessions/${activeSession.sessionId}/cancel`, userId, { method: 'POST' }); setStatus('cancelled'); }; + const openSession = async (session: SessionRecord) => { setHistoryOpen(false); setAnswer(''); setLoadingSession(true); try { await refreshSession(session); } catch (e) { setError((e as Error).message); setStatus('failed'); } finally { setLoadingSession(false); } }; + const cancelRun = async () => { + abortRef.current?.abort(); setStatus('cancelled'); + const session = activeSessionRef.current; + if (session) api(`/api/sessions/${session.sessionId}/cancel`, userId, { method: 'POST' }).catch(() => {}); + }; const usePrompt = (text: string) => { setInput(text); requestAnimationFrame(() => composerRef.current?.focus()); }; const downloadGuide = async () => { if (!activeSession || !files.length) return; @@ -119,15 +169,21 @@ export default function App() {
Connected with care guidanceAI support for families
-
+
HERE WITH YOU

One next step
at a time.

Practical, compassionate support for the moments between appointments.

Think this may be an emergency?Call local emergency services. For fever or infection concerns during treatment, contact your child’s oncology team now and follow their fever plan.
{!answer && status !== 'running' ? <>
START HERE

What would help right now?

You don’t need the perfect words.

{quickPrompts.map(({ icon: Icon, tone, title, text }) => )}
“
There is no right way to feel today.
Small steps still count.
: -
Your LumaCare guide{status === 'running' ? 'Creating a thoughtful response…' : 'Ready to review with your care team'}
{status === 'completed' && files.length > 0 && }
{status === 'running' && !answer ?
Listening carefully and organizing the next steps…
: }{status === 'incomplete' && }
} +
Your LumaCare guide{loadingSession ? 'Opening your saved guide…' : status === 'running' && answer ? 'Writing your guide live…' : status === 'running' ? 'Preparing a thoughtful response…' : status === 'cancelled' ? 'Guide stopped' : 'Ready to review with your care team'}
{status === 'running' && answer && Live}{status === 'completed' && files.length > 0 && }
+
{ const body = event.currentTarget; setIsFollowing(body.scrollHeight - body.scrollTop - body.clientHeight < 48); }}> + {status === 'running' && !answer ?
Putting your guide togetherThis usually takes 15–30 seconds. You can safely stop at any time.
  1. Request received
  2. = 3 ? 'done' : 'active'}>{elapsedSeconds >= 3 ? : }Organizing the next steps
  3. = 10 ? 'active' : ''}>{elapsedSeconds >= 10 ? : 3}Writing your care guide
Working for {elapsedSeconds}s
:
} + {status === 'incomplete' && } +
+ {status === 'running' && answer && !isFollowing && } +
}
Don’t include identifying details