
    ވSj                     f   d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	Z
 ee      j                         j                  j                  dz  Zej                  j!                  dd      Zej                  j!                  d      xs dZed	d
gZ G d d      Z G d d      Z G d d      ZddZy)u  Meeting mode — Jarvis records, transcribes live, and takes notes silently.

Ahmed triggers it ("record this meeting" / "record this online meeting"). While
active, Jarvis DOES NOT reply to what's said — he just transcribes every
utterance into a growing transcript and stays quiet, catching up on context.
He only speaks/answers when Ahmed ADDRESSES him ("Jarvis, …") or types a
question — and answering never pauses the recording (capture continues on the
mic while he thinks). When Ahmed ends it, the full transcript is saved and
summarised into the long-term memory brain, linked to the meeting.

  face-to-face : microphone (the room)
  online       : mic (Ahmed, via AEC) + the laptop's own audio (the remote
                 participants), captured natively with ScreenCaptureKit on a
                 SEPARATE channel — see SystemAudioStream in voice/audio_io.py
                 and _capture_remote in main.py (env flag MEETING_SYSAUDIO).
                 Ahmed's mic is labelled "Ahmed"; the call audio is "them".

Transcription (BOTH modes) goes through multilingual auto-detect Whisper
(MeetingSTT below) so real Saudi/Gulf code-switching — Arabic + English in the
SAME sentence — is decoded natively, instead of the English-only Parakeet the
main loop uses. It runs on a background worker (live where it keeps up, backlog-
tolerant when it can't) and the RAW audio is always written to WAV alongside the
transcript, so a bad/slow transcript can be re-run and nothing is ever lost.

This module holds the session + transcript + the STT/WAV helpers; main.py drives
capture and decides silence-vs-answer. Recordings are written to recordings/.
    )annotationsN)Path
recordingsMEETING_STT_MODELz"mlx-community/whisper-large-v3-mlxMEETING_STT_LANGz$mlx-community/whisper-large-v3-turbozmlx-community/whisper-small-mlxc                  d    e Zd ZdddZdddZddZedd       ZdddZddZ	ddZ
dd	Zdd
Zy)MeetingSessionc                r   || _         |xs d| _        t        j                         | _        g | _        dj                  d | j                  D              d d j                         j                  dd      }dt        j                  dt        j                  | j                               d|xs d	 | _
        y )
Nmeeting c              3  L   K   | ]  }|j                         s|d v r|nd  yw)z -_r   N)isalnum).0cs     ;/Users/ahmed/devFolder/Ultron/claude-voice/voice/meeting.py	<genexpr>z*MeetingSession.__init__.<locals>.<genexpr>>   s+      , *1 IIK1:q2= *s   "$(    _meeting_z%Y%m%d_%H%Mnotes)modetitletimestartedlinesjoinstripreplacestrftime	localtimestem)selfr   r   safes       r   __init__zMeetingSession.__init__7   s    	'i
yy{ 46
ww , $

, ,,/R116c9J 	  mT^^DLL=Y Z[)+	    Nc                    |xs dj                         }|r5| j                  j                  ||nt        j                         ||f       y y )Nr   )r   r   appendr   )r#   speakertexttss       r   addzMeetingSession.addD   sA    
!!#JJR^rgtTU r&   c                2    t        | j                  d       S )Nc                    | d   S )Nr    )xs    r   <lambda>z)MeetingSession._ordered.<locals>.<lambda>J   s    !r&   )key)sortedr   r#   s    r   _orderedzMeetingSession._orderedI   s    djjn55r&   c                J    t        j                          | j                  z
  dz  S )Ng      N@)r   r   r4   s    r   minuteszMeetingSession.minutesL   s    		dll*d22r&   c                V    dj                  d | j                         | d D              S )zqThe tail of the transcript, for answering a mid-meeting question
        with full context of what was just said.
c              3  2   K   | ]  \  }}}| d |   yw): Nr/   )r   _tsptxs       r   r   z(MeetingSession.recent.<locals>.<genexpr>S   s#     N9M:2r2B4r"9Ms   N)r   r5   )r#   ns     r   recentzMeetingSession.recentP   s(     yyN!9MNNNr&   c                    d| j                    d| j                   dt        j                  d       ddg}| j	                         D ]  \  }}}|j                  | d|         dj                  |      S )	Nz# z  (z, z%Y-%m-%d %H:%M)r   r;   r9   )r   r   r   r    r5   r(   r   )r#   outr<   r=   r>   s        r   	full_textzMeetingSession.full_textU   st    DJJ<s499+R-- 012!568:--/JBBJJ"Rt}% *yy~r&   c                .    t         | j                   dz  S )z$Mic/room channel (Ahmed + the room).z	_room.wav_RECr"   r4   s    r   room_wav_pathzMeetingSession.room_wav_path\       9---r&   c                .    t         | j                   dz  S )u?   System-audio channel (the remote participants) — online only.z	_them.wavrF   r4   s    r   them_wav_pathzMeetingSession.them_wav_path`   rI   r&   c                    t         j                  d       t         | j                   dz  }|j                  | j	                         d       t        |      S )z=Write the transcript to recordings/ and return the file path.Texist_okz.mdzutf-8)encoding)rG   mkdirr"   
write_textrD   strr#   paths     r   savezMeetingSession.saved   sF    

D
!3''(7;4yr&   )	in_personr   )r   rR   r   rR   returnNoneN)r)   rR   r*   rR   r+   zfloat | NonerW   rX   )rW   zlist[tuple[float, str, str]]rW   float)r   )r?   intrW   rR   rW   rR   )rW   r   )__name__
__module____qualname__r%   r,   r5   propertyr7   r@   rD   rH   rK   rU   r/   r&   r   r	   r	   6   s@    ,V
6 3 3O
..r&   r	   c                  :    e Zd ZdZddZddZddZed	d       Zy)
WavRecorderu3  Incremental 16 kHz mono int16 WAV writer (stdlib `wave` → a proper RIFF
    header). Writes to disk as audio arrives (low RAM; a crash leaves only a
    slightly-wrong length field, data is still recoverable). This is the raw-
    audio safety net: every meeting is re-transcribable even if live STT lags.c                |   t        |      | _        t        j                  d       t	        j
                  | j                  d      | _        | j                  j                  d       | j                  j                  d       | j                  j                  d       d| _
        t        j                         | _        y )NTrM   wb      >  r   )rR   rT   rG   rP   waveopen_wfsetnchannelssetsampwidthsetframeratesamples	threadingLock_lockrS   s     r   r%   zWavRecorder.__init__r   s|    I	

D
!+/99TYY+Ea a e$^^%
r&   c                t   |t        |      dk(  ry t        j                  |dd      dz  j                  d      }| j                  5  | j
                  
	 d d d        y | j
                  j                  |j                                | xj                  t        |      z  c_        d d d        y # 1 sw Y   y xY w)Nr   g      g      ?g    @z<i2)	lennpclipastyperr   rk   writeframestobytesro   )r#   audiopcm16s      r   writezWavRecorder.write|   s    =CJ!Oc*W4<<UCZZxx Z HH  1LLCJ&L	 ZZs   B.AB..B7c                    | j                   5  | j                  "	 | j                  j                          d | _        d d d        y # t        $ r Y w xY w# 1 sw Y   y xY wrY   )rr   rk   close	Exceptionr4   s    r   r~   zWavRecorder.close   sR    ZZxx#HHNN$   Z ! 	 Zs-   AAA	AAAAAc                     | j                   dz  S )Ng     @@)ro   r4   s    r   secondszWavRecorder.seconds   s    ||g%%r&   NrW   rX   )rz   
np.ndarrayrW   rX   rZ   )	r^   r_   r`   __doc__r%   r|   r~   ra   r   r/   r&   r   rc   rc   l   s+    R
&'  & &r&   rc   c                       e Zd ZdZddZddZy)
MeetingSTTu  Multilingual auto-detect Whisper (mlx-whisper) for meeting transcription.

    language=None → Whisper detects per-utterance and keeps Arabic+English
    code-switching inline (proven on Ahmed's live audio). All MLX inference is
    pinned to voice.stt._MLX — the single Metal-affine thread — so it serialises
    with (and never corrupts) the main loop's Parakeet stream.
    c                |      _         ddlm} | _        d fd}|j                  |      j	                          y )Nr   )_MLXc                     dd l } | _        j                  j                  t        j                  dt        j
                        t               y )Nr   rh   dtypepath_or_hf_repolanguage)mlx_whisper_whisper
transcriberu   zerosfloat32r   )r   repor#   s    r   _loadz"MeetingSTT.__init__.<locals>._load   s?    'DM MM$$bjj1 $/? % Ar&   r   )r   	voice.sttr   submitresult)r#   r   r   r   s   ``  r   r%   zMeetingSTT.__init__   s1    	"		A 	E!!#r&   c                f     d fd} j                   j                  |      j                         S )z=audio: 1-D float32 mono @ 16 kHz -> code-switched transcript.c                     j                   j                  t        j                  t        j                        j
                  t              } | j                  d      xs dj                         S )Nr   r   r*   r   )	r   r   ru   ascontiguousarrayr   r   r   getr   )rC   rz   r#   s    r   _doz"MeetingSTT.transcribe.<locals>._do   sY    --**$$U"**= $		4D + FC GGFO)r0022r&   r]   )r   r   r   )r#   rz   r   s   `` r   r   zMeetingSTT.transcribe   s(    	3
 yy$++--r&   N)r   rR   rW   rX   )rz   r   rW   rR   )r^   r_   r`   r   r%   r   r/   r&   r   r   r      s    $.r&   r   c            	         d} t               }t        D ]+  }|r||v r
|j                  |       	 t        |      }||fc S  t        d|        # t        $ r}|} t        d| d| d       Y d}~^d}~ww xY w)u   Load the multilingual meeting STT, degrading through the fallback chain.
    Returns (stt, model_repo). Raises only if EVERY option fails (then main.py
    falls back to the main-loop STT). Blocking — call via asyncio.to_thread.Nz  [meeting-stt] z unavailable (z); trying nextzno meeting STT could load: )set_STT_FALLBACKSr,   r   r   printRuntimeError)lasttriedr   sttes        r   load_meeting_sttr      s     "DeEtu}		$	LT"C9  4TF;
<<  	LD$TF.>JKK	Ls   A	A6A11A6)rW   ztuple[MeetingSTT, str])r   
__future__r   osrp   r   ri   pathlibr   numpyru   __file__resolveparentrG   environr   r   r   r   r	   rc   r   r   r/   r&   r   <module>r      s   6 # 	     H~&&--< JJNN=? ::>>"45=  *%3 3l%& %&P. .D=r&   