@@ -1918,18 +1918,85 @@ async function postChatMsg(cardData) {
19181918
19191919const TranscriptCtrl = {
19201920 _liveEl : null ,
1921+ _interimCardEl : null ,
1922+ _interimProfile : null ,
19211923
19221924 init ( ) {
19231925 this . _liveEl = document . getElementById ( 'live-transcript' ) ;
19241926 } ,
19251927
19261928 showInterim ( text ) {
1927- if ( ! this . _liveEl ) return ;
1928- this . _liveEl . textContent = text ;
1929- this . _liveEl . classList . toggle ( 'speaking' , text . length > 0 ) ;
19301929 if ( text && ! State . currentUtteranceStartedAt ) {
19311930 State . currentUtteranceStartedAt = Date . now ( ) ;
19321931 }
1932+
1933+ // Always clear the bottom strip; it serves as fallback only
1934+ if ( this . _liveEl ) {
1935+ this . _liveEl . textContent = '' ;
1936+ this . _liveEl . classList . remove ( 'speaking' ) ;
1937+ }
1938+
1939+ if ( ! text ) {
1940+ this . _removeInterimCard ( ) ;
1941+ return ;
1942+ }
1943+
1944+ // Show inline inside the active speaker's lane when one exists
1945+ const profile = profileById ( State . activeSpeakerId ) ;
1946+ if ( profile && profile . cardsEl ) {
1947+ this . _updateInterimCard ( text , profile ) ;
1948+ } else if ( this . _liveEl ) {
1949+ // Fallback: bottom strip when no lane has been created yet
1950+ this . _liveEl . textContent = text ;
1951+ this . _liveEl . classList . add ( 'speaking' ) ;
1952+ }
1953+ } ,
1954+
1955+ // Create or refresh the inline interim card for the given profile.
1956+ _updateInterimCard ( text , profile ) {
1957+ // If the speaker changed mid-utterance, discard the previous card
1958+ if ( this . _interimProfile && this . _interimProfile !== profile ) {
1959+ this . _removeInterimCard ( ) ;
1960+ }
1961+
1962+ if ( ! this . _interimCardEl ) {
1963+ const card = document . createElement ( 'article' ) ;
1964+ card . className = 'card card-tone-mid card-interim-live' ;
1965+ card . setAttribute ( 'role' , 'status' ) ;
1966+ card . setAttribute ( 'aria-live' , 'polite' ) ;
1967+ card . setAttribute ( 'aria-label' , 'Speech in progress' ) ;
1968+ card . style . setProperty ( '--speaker-color' , profile . color ) ;
1969+
1970+ const textEl = document . createElement ( 'span' ) ;
1971+ textEl . className = 'interim-live-text' ;
1972+ card . appendChild ( textEl ) ;
1973+
1974+ const dot = document . createElement ( 'span' ) ;
1975+ dot . className = 'interim-live-dot' ;
1976+ dot . setAttribute ( 'aria-hidden' , 'true' ) ;
1977+ card . appendChild ( dot ) ;
1978+
1979+ profile . cardsEl . appendChild ( card ) ;
1980+ this . _interimCardEl = card ;
1981+ this . _interimProfile = profile ;
1982+ }
1983+
1984+ const textEl = this . _interimCardEl . querySelector ( '.interim-live-text' ) ;
1985+ if ( textEl ) textEl . textContent = text ;
1986+
1987+ // Only scroll if the user is already near the bottom to avoid interrupting review
1988+ const el = this . _interimProfile . cardsEl ;
1989+ if ( el . scrollHeight - el . scrollTop - el . clientHeight < 80 ) {
1990+ el . scrollTop = el . scrollHeight ;
1991+ }
1992+ } ,
1993+
1994+ _removeInterimCard ( ) {
1995+ if ( this . _interimCardEl ) {
1996+ this . _interimCardEl . remove ( ) ;
1997+ this . _interimCardEl = null ;
1998+ this . _interimProfile = null ;
1999+ }
19332000 } ,
19342001
19352002 clearInterim ( ) {
0 commit comments