1616 button : hover { border-color : # 888 ; }
1717 .row { display : flex; gap : 10px ; flex-wrap : wrap; align-items : center; }
1818 select { padding : 6px ; border-radius : 8px ; border : 1px solid # bbb ; max-width : 360px ; }
19- .pill { border : 1px solid # ddd ; border-radius : 999px ; padding : 4px 9px ; }
19+ .pill { border : 1px solid # ddd ; border-radius : 999px ; padding : 4px 9px ; font-size : 0.85 em ; }
2020 .card { border : 1px solid # ddd ; border-radius : 10px ; padding : 12px ; margin-top : 14px ; }
21+ # statusPanel { background : # f8f9fa ; }
22+ # statusPanel strong { color : # 495057 ; }
23+ # memoryPanel { background : # fefefe ; max-height : 250px ; overflow-y : auto; }
24+ # toolDetails { background : # f1f3f4 ; padding : 6px 10px ; border-radius : 6px ; font-family : monospace; font-size : 0.85em ; }
2125 .debug-log { max-height : 180px ; overflow-y : auto; background : # f7f7f7 ; border : 1px solid # eee ; padding : 8px ; border-radius : 8px ; }
2226 .debug-tools { display : grid; gap : 10px ; margin-top : 10px ; }
2327 .debug-tool { border : 1px dashed # ccc ; padding : 8px ; border-radius : 8px ; }
@@ -96,6 +100,28 @@ <h1>Chat</h1>
96100 < input type ="text " id ="speaker_name_input " placeholder ="e.g. Major " style ="min-width: 260px; " />
97101 </ div >
98102
103+ <!-- Speaker & Tool Status Panel -->
104+ < div class ="card " id ="statusPanel " style ="display: none; ">
105+ < div class ="row " style ="gap: 20px; ">
106+ < div id ="speakerStatus ">
107+ < strong > Speaker:</ strong > < span id ="currentSpeaker "> Unknown</ span >
108+ < span id ="speakerBadge " class ="pill " style ="display: none; "> </ span >
109+ </ div >
110+ < div id ="toolStatus " style ="display: none; ">
111+ < strong > Tools:</ strong > < span id ="toolIterations "> -</ span >
112+ < span id ="toolProgress " class ="pill "> </ span >
113+ </ div >
114+ </ div >
115+ < div id ="toolDetails " class ="meta " style ="margin-top: 8px; display: none; "> </ div >
116+ </ div >
117+
118+ <!-- Memory Context Panel -->
119+ < div class ="card " id ="memoryPanel " style ="display: none; ">
120+ < h3 style ="margin-top: 0; "> Recent Context</ h3 >
121+ < div id ="memoryList " style ="max-height: 150px; overflow-y: auto; "> </ div >
122+ < button onclick ="loadMemories() " class ="btn " style ="margin-top: 8px; "> Refresh Memories</ button >
123+ </ div >
124+
99125 < div class ="card " id ="debugCard ">
100126 < h2 style ="margin-top:0 "> Debug</ h2 >
101127 < div class ="meta "> Verbose logging is {{ 'enabled' if state.verbose else 'disabled' }} (toggle in < a href ="/settings "> Settings</ a > ).</ div >
@@ -158,6 +184,114 @@ <h2 style="margin-top:0">Debug</h2>
158184 debugOutput . textContent = text || '' ;
159185 }
160186
187+ // Speaker and tool status management
188+ const statusPanel = document . getElementById ( 'statusPanel' ) ;
189+ const currentSpeakerEl = document . getElementById ( 'currentSpeaker' ) ;
190+ const speakerBadgeEl = document . getElementById ( 'speakerBadge' ) ;
191+ const toolStatusEl = document . getElementById ( 'toolStatus' ) ;
192+ const toolIterationsEl = document . getElementById ( 'toolIterations' ) ;
193+ const toolProgressEl = document . getElementById ( 'toolProgress' ) ;
194+ const toolDetailsEl = document . getElementById ( 'toolDetails' ) ;
195+ const memoryPanel = document . getElementById ( 'memoryPanel' ) ;
196+ const memoryListEl = document . getElementById ( 'memoryList' ) ;
197+
198+ function updateSpeakerStatus ( speakerInfo ) {
199+ if ( ! speakerInfo ) return ;
200+ statusPanel . style . display = 'block' ;
201+
202+ const name = speakerInfo . speaker_name || speakerInfo . current_speaker_name || 'Unknown' ;
203+ const voiceId = speakerInfo . voice_id || speakerInfo . current_speaker_voice_id || '' ;
204+ const isNew = speakerInfo . is_new_speaker || speakerInfo . is_new_voice || false ;
205+
206+ currentSpeakerEl . textContent = name ;
207+
208+ if ( isNew ) {
209+ speakerBadgeEl . textContent = 'New Speaker' ;
210+ speakerBadgeEl . style . background = '#ffe066' ;
211+ speakerBadgeEl . style . display = 'inline-block' ;
212+ } else if ( voiceId ) {
213+ speakerBadgeEl . textContent = 'Recognized' ;
214+ speakerBadgeEl . style . background = '#90EE90' ;
215+ speakerBadgeEl . style . display = 'inline-block' ;
216+ } else {
217+ speakerBadgeEl . style . display = 'none' ;
218+ }
219+ }
220+
221+ function updateToolStatus ( toolInfo ) {
222+ if ( ! toolInfo || ! toolInfo . tool_calls || toolInfo . tool_calls . length === 0 ) {
223+ toolStatusEl . style . display = 'none' ;
224+ toolDetailsEl . style . display = 'none' ;
225+ return ;
226+ }
227+
228+ statusPanel . style . display = 'block' ;
229+ toolStatusEl . style . display = 'block' ;
230+
231+ const iterations = toolInfo . iterations || 1 ;
232+ const calls = toolInfo . tool_calls || [ ] ;
233+
234+ toolIterationsEl . textContent = `${ iterations } iteration(s)` ;
235+ toolProgressEl . textContent = `${ calls . length } tool(s) executed` ;
236+
237+ // Show tool details
238+ if ( calls . length > 0 ) {
239+ toolDetailsEl . style . display = 'block' ;
240+ toolDetailsEl . innerHTML = calls . map ( c =>
241+ `<div>• ${ c . name } : ${ JSON . stringify ( c . input ) . substring ( 0 , 50 ) } ...</div>`
242+ ) . join ( '' ) ;
243+ }
244+ }
245+
246+ function displayMemoryItem ( memory ) {
247+ const div = document . createElement ( 'div' ) ;
248+ div . className = 'msg' ;
249+ div . style . fontSize = '0.9em' ;
250+ div . style . padding = '4px 8px' ;
251+
252+ const kind = memory . kind || 'UNKNOWN' ;
253+ const text = memory . text || '' ;
254+ const speaker = memory . speaker_id || memory . meta ?. speaker_id || '' ;
255+
256+ // Color by kind
257+ const kindColors = {
258+ 'FACT' : '#e3f2fd' ,
259+ 'PREFERENCE' : '#fff3e0' ,
260+ 'AI_INSIGHT' : '#f3e5f5' ,
261+ 'RELATIONSHIP' : '#e8f5e9' ,
262+ 'ACTION' : '#fce4ec' ,
263+ 'META' : '#f5f5f5'
264+ } ;
265+ div . style . background = kindColors [ kind ] || '#fff' ;
266+
267+ let html = `<strong>[${ kind } ]</strong> ${ text } ` ;
268+ if ( speaker ) html += ` <em>(${ speaker } )</em>` ;
269+ div . innerHTML = html ;
270+
271+ return div ;
272+ }
273+
274+ async function loadMemories ( ) {
275+ try {
276+ const resp = await fetch ( '/api/memories?limit=20' ) ;
277+ if ( ! resp . ok ) throw new Error ( 'Failed to load memories' ) ;
278+ const data = await resp . json ( ) ;
279+
280+ memoryPanel . style . display = 'block' ;
281+ memoryListEl . innerHTML = '' ;
282+
283+ ( data . memories || [ ] ) . forEach ( m => {
284+ memoryListEl . appendChild ( displayMemoryItem ( m ) ) ;
285+ } ) ;
286+
287+ if ( ! data . memories || data . memories . length === 0 ) {
288+ memoryListEl . innerHTML = '<div class="meta">No memories found</div>' ;
289+ }
290+ } catch ( e ) {
291+ appendMeta ( 'Failed to load memories: ' + e ) ;
292+ }
293+ }
294+
161295 function setAsrStatus ( t ) {
162296 asrStatus . textContent = 'ASR: ' + t ;
163297 }
@@ -470,6 +604,21 @@ <h2 style="margin-top:0">Debug</h2>
470604 if ( data . reply_text ) appendMsg ( 'agent' , data . reply_text ) ;
471605 if ( data . actions && data . actions . length ) appendMeta ( 'actions: ' + JSON . stringify ( data . actions ) ) ;
472606 if ( data . audio ) playAgentAudio ( data . audio ) ;
607+ // Update speaker status from response
608+ if ( data . speaker_info || data . speaker_name || data . voice_id ) {
609+ updateSpeakerStatus ( {
610+ speaker_name : data . speaker_name || data . speaker_info ?. speaker_name ,
611+ voice_id : data . voice_id || data . speaker_info ?. voice_id ,
612+ is_new_speaker : data . is_new_voice || data . speaker_info ?. is_new_speaker
613+ } ) ;
614+ }
615+ // Update tool status from response
616+ if ( data . tool_info || data . tool_calls ) {
617+ updateToolStatus ( {
618+ iterations : data . tool_info ?. iterations || 1 ,
619+ tool_calls : data . tool_calls || data . tool_info ?. tool_calls || [ ]
620+ } ) ;
621+ }
473622 } else if ( msg . type === 'error' ) {
474623 appendMeta ( 'ASR error: ' + ( msg . error || 'unknown' ) ) ;
475624 }
@@ -496,11 +645,13 @@ <h2 style="margin-top:0">Debug</h2>
496645 const persona = ( document . getElementById ( 'persona_input' ) . value || '' ) . trim ( ) ;
497646 const speakerName = ( document . getElementById ( 'speaker_name_input' ) . value || '' ) . trim ( ) ;
498647
648+ // Use actual session from state, not hardcoded value
649+ const sessionId = '{{ state.selected_session or "" }}' || 'session-' + Date . now ( ) ;
499650 ws . send ( JSON . stringify ( {
500651 type : 'start' ,
501652 language_code : ( languageSel . value || 'en-US' ) ,
502653 sample_rate_hz : 16000 ,
503- session_id : 'ui-session' ,
654+ session_id : sessionId ,
504655 voice_id : selectedMicDeviceId || null ,
505656 speaker_name : speakerName || null ,
506657 personality_prompt : persona || null ,
0 commit comments