11import { useEffect , useRef , useState } from "react" ;
2- import { useWebChat , type ToolActivity } from "@/hooks/useWebChat" ;
2+ import { useWebChat , getPortalChatSessionId , type ToolActivity } from "@/hooks/useWebChat" ;
3+ import type { ActiveWorker } from "@/hooks/useChannelLiveState" ;
4+ import { useLiveContext } from "@/hooks/useLiveContext" ;
35import { Markdown } from "@/components/Markdown" ;
46
57interface WebChatPanelProps {
@@ -38,6 +40,33 @@ function ThinkingIndicator() {
3840 ) ;
3941}
4042
43+ function ActiveWorkersPanel ( { workers } : { workers : ActiveWorker [ ] } ) {
44+ if ( workers . length === 0 ) return null ;
45+
46+ return (
47+ < div className = "rounded-lg border border-amber-500/25 bg-amber-500/5 px-3 py-2" >
48+ < div className = "mb-2 flex items-center gap-1.5 text-tiny text-amber-200" >
49+ < div className = "h-1.5 w-1.5 animate-pulse rounded-full bg-amber-400" />
50+ < span >
51+ { workers . length } active worker{ workers . length !== 1 ? "s" : "" }
52+ </ span >
53+ </ div >
54+ < div className = "flex flex-col gap-1.5" >
55+ { workers . map ( ( worker ) => (
56+ < div key = { worker . id } className = "flex min-w-0 items-center gap-2 rounded-md bg-amber-500/10 px-2.5 py-1.5 text-tiny" >
57+ < span className = "font-medium text-amber-300" > Worker</ span >
58+ < span className = "min-w-0 flex-1 truncate text-ink-dull" > { worker . task } </ span >
59+ < span className = "shrink-0 text-ink-faint" > { worker . status } </ span >
60+ { worker . currentTool && (
61+ < span className = "max-w-40 shrink-0 truncate text-amber-400/80" > { worker . currentTool } </ span >
62+ ) }
63+ </ div >
64+ ) ) }
65+ </ div >
66+ </ div >
67+ ) ;
68+ }
69+
4170function FloatingChatInput ( {
4271 value,
4372 onChange,
@@ -125,12 +154,16 @@ function FloatingChatInput({
125154
126155export function WebChatPanel ( { agentId } : WebChatPanelProps ) {
127156 const { messages, isStreaming, error, toolActivity, sendMessage } = useWebChat ( agentId ) ;
157+ const { liveStates } = useLiveContext ( ) ;
128158 const [ input , setInput ] = useState ( "" ) ;
129159 const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
160+ const sessionId = getPortalChatSessionId ( agentId ) ;
161+ const activeWorkers = Object . values ( liveStates [ sessionId ] ?. workers ?? { } ) ;
162+ const hasActiveWorkers = activeWorkers . length > 0 ;
130163
131164 useEffect ( ( ) => {
132165 messagesEndRef . current ?. scrollIntoView ( { behavior : "smooth" } ) ;
133- } , [ messages . length , isStreaming , toolActivity . length ] ) ;
166+ } , [ messages . length , isStreaming , toolActivity . length , activeWorkers . length ] ) ;
134167
135168 const handleSubmit = ( ) => {
136169 const trimmed = input . trim ( ) ;
@@ -144,6 +177,12 @@ export function WebChatPanel({ agentId }: WebChatPanelProps) {
144177 { /* Messages */ }
145178 < div className = "flex-1 overflow-y-auto" >
146179 < div className = "mx-auto flex max-w-2xl flex-col gap-6 px-4 py-6 pb-32" >
180+ { hasActiveWorkers && (
181+ < div className = "sticky top-0 z-10 bg-app/90 pb-2 pt-2 backdrop-blur-sm" >
182+ < ActiveWorkersPanel workers = { activeWorkers } />
183+ </ div >
184+ ) }
185+
147186 { messages . length === 0 && ! isStreaming && (
148187 < div className = "flex flex-col items-center justify-center py-24" >
149188 < p className = "text-sm text-ink-faint" >
0 commit comments