Download code/server.py from laion/tyger-speaker-predictor: direct link, hf CLI and curl.
- Browser
- Download file 30 kB
-
https://huggingface.co/laion/tyger-speaker-predictor/resolve/main/code/server.py
- Command line
-
hf download hf://laion/tyger-speaker-predictor/code/server.py
-
curl -L -o server.py https://huggingface.co/laion/tyger-speaker-predictor/resolve/main/code/server.py
30 kB
| """Robo page + unchanged TYGER Conversation and EARS, sharing the Playground TTS.""" | |
| import asyncio | |
| import json | |
| import logging | |
| import os | |
| from pathlib import Path | |
| import time | |
| import numpy as np | |
| from aiohttp import web, WSMsgType | |
| import httpx | |
| import paths | |
| from app.ears.conversation import Conversation | |
| from app.plugins.base import PluginContext | |
| from app.plugins.ears.tyger.plugin import Tyger | |
| from tts_bridge import MouthBridge | |
| from app.ears.playback import mic_block | |
| from audio_sync import estimate_echo_delay | |
| from feature_tap import attach_feature_tap,local_only,extract,extract_context,extract_layers,transcribe_check | |
| from identity import IdentityManager | |
| from speaker_teacher import preserve_encoder_precision | |
| preserve_encoder_precision() | |
| ROOT=paths.ROOT | |
| log=logging.getLogger('robo') | |
| STATE={'ready':False,'error':None,'phase':'Starting Nemotron / TYGER','ears':None} | |
| class RoboConversation(Conversation): | |
| async def _on_text(self,payload): | |
| data=json.loads(payload) | |
| if data.get('status_only'): | |
| await self.ws.send_json({'type':'service_status','text':str(data.get('text',''))[:300],'stage':data.get('stage')}) | |
| return | |
| if self.speaker_ext is not None:await self.speaker_ext.metadata(data) | |
| await self.ws.send_json({'type':'backchannel' if data.get('bc') else 'reply','text':data['text'], | |
| 'reply_id':self.playback.rid,'performance':data.get('performance'), | |
| 'speaker_id':(data.get('speaker') or {}).get('speaker_id'),'speaker_name':(data.get('speaker') or {}).get('display_name'), | |
| 'speaker_update':data.get('speaker_update')}) | |
| def __init__(self,*args,**kwargs): | |
| super().__init__(*args,**kwargs) | |
| self.identity_enabled=False;self.predictor=None;self.last_identity_notice=0 | |
| self.identity_mode='ecapa';self.teacher=None;self.teacher_stream=None | |
| self.identity_audio=np.zeros(0,np.float32) | |
| selection={};robo=None | |
| ref=ROOT/'speaker_v2/artifacts/robo_reference.npz' | |
| if ref.exists(): | |
| try:robo=np.load(ref)['embedding'] | |
| except Exception:pass | |
| checkpoint=ROOT/'speaker_v2/artifacts/best.pt' | |
| if checkpoint.exists(): | |
| try: | |
| from speaker_v2.inference import Predictor | |
| self.predictor=Predictor(checkpoint) | |
| path=ROOT/'speaker_v2/artifacts/selection.json' | |
| if path.exists():selection=json.loads(path.read_text()) | |
| except Exception as exc:log.warning('Speaker pilot unavailable: %s',type(exc).__name__) | |
| self.student_threshold=float(selection.get('verification_threshold',.65)) | |
| self.teacher_threshold=.58 # conservative validation-EER operating point, not authentication | |
| self.identity=IdentityManager(threshold=self.teacher_threshold,robo=robo) | |
| self.speaker_ext=None;self.current_speaker=None | |
| self.input_samples=0;self.input_blocks=0;self.last_input_notice=0. | |
| self.pending_barge_proposal=None | |
| self.native_barge_settings=(self.session.barge_thr,self.session.barge_deb) | |
| try: | |
| from speaker_session import SpeakerSession | |
| self.speaker_ext=SpeakerSession(self,long_context='long') | |
| self.identity_mode='v4-long';self.identity_enabled=True | |
| self._speaker_barge_sensitivity(True) | |
| except Exception as exc:log.warning('Completed speaker student unavailable: %s',type(exc).__name__) | |
| def _speaker_barge_sensitivity(self,enabled): | |
| # The frozen GRU is unchanged. With full acoustic confirmation we can | |
| # use its supported sensitive operating point without trusting weak | |
| # short speaker cues or turning bot echo into an interruption. | |
| threshold,debounce=(.20,1) if enabled else self.native_barge_settings | |
| self.ears.set_threshold(self.session,'barge_thr',threshold) | |
| self.ears.set_threshold(self.session,'barge_deb',debounce) | |
| def _ready_message(self): | |
| return {**super()._ready_message(),'identity_enabled':self.identity_enabled,'identity_mode':self.identity_mode, | |
| 'speaker_instruction':'Say: Hey Robbie, I am Christoph. Only enrolled voices keep their turn open.'} | |
| async def _send_turn(self,transcript): | |
| self._start_speaking() | |
| await self.to_mouth(b'T',{'transcript':transcript,'speaker':self.current_speaker}) | |
| def _start_speaking(self): | |
| # Identity evidence for an interruption must be NEW speech, not the | |
| # tail of the very utterance whose answer is now being prepared. | |
| self.speaker_reply_start_s=self.hop_pos/self.ears.sr | |
| super()._start_speaking() | |
| async def run(self): | |
| if self.speaker_ext is not None:self.speaker_ext.start() | |
| try:await super().run() | |
| finally: | |
| if self.speaker_ext is not None:await self.speaker_ext.close() | |
| task=getattr(self,'calibration_task',None) | |
| if task is not None:task.cancel() | |
| self.identity.clear();self._reset_identity_context() | |
| self.cal_mic=[];self.cal_ref=[] | |
| async def _teacher(self): | |
| if self.teacher is None: | |
| from speaker_teacher import get_cached_teacher,StreamingTeacher | |
| self.teacher=await asyncio.to_thread(get_cached_teacher) | |
| self.teacher_stream=StreamingTeacher(self.teacher) | |
| return self.teacher | |
| def _reset_identity_context(self): | |
| self.identity.reset_utterance();self.identity_audio=np.zeros(0,np.float32) | |
| if self.predictor is not None:self.predictor.reset() | |
| if self.teacher_stream is not None:self.teacher_stream.reset() | |
| async def _on_events(self,events,hop_end): | |
| if self.identity_enabled and self.speaker_ext is not None and self.identity_mode.startswith('v4'): | |
| proposals=[e for e in events if e['kind']=='BARGE'] | |
| if proposals and self.speaking:self.pending_barge_proposal=(max(proposals,key=lambda e:e.get('yield_prob',0)),hop_end) | |
| if proposals and getattr(self,'synthetic_diagnostics',False): | |
| await self.ws.send_json({'type':'synthetic_barge_proposal','audio_s':hop_end/self.ears.sr, | |
| 'proposals':proposals,'speaker_cue':self.speaker_ext.last_activity}) | |
| # Recognition is useful feedback even when enrollment rejects a | |
| # turn. A preview is NEVER an endpoint and never calls the LLM. | |
| if not self.speaking: | |
| for event in events: | |
| if event['kind']=='PARTIAL': | |
| self.speaker_ext.note_asr(event['text'],hop_end/self.ears.sr) | |
| await self.ws.send_json({'type':'asr_preview','text':event['text'], | |
| 'status':'not_submitted','source':'native_asr'}) | |
| for event in events: | |
| if event['kind']=='SPEAKER_FEATURES':await self.speaker_ext.observe(event,hop_end) | |
| # Native GRU still runs unchanged. It no longer controls the | |
| # enrolled speaker's submit clock or transcribes background voices | |
| # into a submitted turn. Known users retain native interruption. | |
| filtered=[e for e in events if e['kind']=='FRAME'] | |
| pending=self.pending_barge_proposal | |
| if pending and self.speaking and 0<=hop_end-pending[1]<=1.28*self.ears.sr: | |
| confirmed=await self.speaker_ext.confirm_interrupt(pending[0],hop_end) | |
| if confirmed:filtered.append(pending[0]);self.pending_barge_proposal=None | |
| elif pending and (not self.speaking or hop_end-pending[1]>1.28*self.ears.sr):self.pending_barge_proposal=None | |
| if getattr(self,'calibrating',False):filtered=[e for e in filtered if e['kind']=='FRAME'] | |
| await super()._on_events(filtered,hop_end);return | |
| for event in events: | |
| if event['kind']=='SPEAKER_FEATURES': | |
| energy=float(np.sqrt(np.mean(10**(np.asarray(event['energy_db'])/10)))) | |
| prediction=self.predictor.update(event['features'],energy=energy) if self.predictor is not None else {'kind':'warming'} | |
| audio=event['audio16'] | |
| if self.identity_enabled or self.identity_mode=='student': | |
| self.identity_audio=np.r_[self.identity_audio,audio][-(32000 if self.speaking else 320000):] | |
| identity=None | |
| if self.identity_mode=='student':identity=self.identity.observe(prediction) | |
| elif self.identity_enabled: | |
| try: | |
| await self._teacher() | |
| cue=await asyncio.to_thread(self.teacher_stream.update,audio,speech=bool(self.session.in_turn and not self.speaking)) | |
| identity=self.identity.observe(cue) | |
| except Exception as exc: | |
| self.identity_enabled=False | |
| await self.ws.send_json({'type':'identity','enabled':False,'mode':self.identity_mode,**self.identity.status()}) | |
| await self.ws.send_json({'type':'warning','text':'ECAPA unavailable; speaker filter disabled ('+type(exc).__name__+').'}) | |
| if identity is None:continue | |
| if time.monotonic()-self.last_identity_notice>.8: | |
| self.last_identity_notice=time.monotonic() | |
| await self.ws.send_json({'type':'identity','prediction':identity,'enabled':self.identity_enabled,**self.identity.status()}) | |
| filtered=[e for e in events if e['kind']!='SPEAKER_FEATURES'] | |
| if getattr(self,'calibrating',False):filtered=[e for e in filtered if e['kind']=='FRAME'] | |
| await super()._on_events(filtered,hop_end) | |
| async def _on_endpoint(self,event): | |
| if self.identity_enabled or self.identity_mode=='student': | |
| if self.identity_mode=='ecapa' and self.identity_enabled: | |
| try: | |
| teacher=await self._teacher() | |
| audio=self.identity_audio | |
| # The complete recognized utterance, not just the wake words. | |
| active=np.flatnonzero(np.abs(audio)>.003) | |
| if len(active):audio=audio[max(0,active[0]-320):min(len(audio),active[-1]+321)] | |
| embedding=await asyncio.to_thread(teacher.embed,audio) | |
| self.identity.reset_utterance() | |
| # Whole-utterance evidence is one embedding, not repeated frames. | |
| self.identity.observe({'embedding':embedding,'kind':'speech','confidence':.8}) | |
| self.identity.voiced_ms=round(len(audio)/16) | |
| except Exception: | |
| self.identity.reset_utterance() | |
| decision=self.identity.finish(event.get('transcript') or '',enabled=self.identity_enabled) | |
| if self.predictor is not None:self.predictor.reset() | |
| if self.teacher_stream is not None:self.teacher_stream.reset() | |
| self.identity_audio=np.zeros(0,np.float32) | |
| await self.ws.send_json({'type':'identity','decision':decision,'enabled':self.identity_enabled,**self.identity.status()}) | |
| if not decision['accepted']: | |
| await self.ws.send_json({'type':'ignored','reason':decision.get('reason','Unenrolled or uncertain speaker')}) | |
| return | |
| await super()._on_endpoint(event) | |
| async def _on_barge(self,event,hop_end): | |
| self.pending_barge_proposal=None | |
| generated=self.mouth_done | |
| if generated and self.have_mouth:await self.to_mouth(b'H',{'interrupted':True}) | |
| if getattr(self,'calibrating',False): | |
| self.calibrating=False;self.cal_mic=[];self.cal_ref=[] | |
| if getattr(self,'calibration_task',None) is not None:self.calibration_task.cancel() | |
| await super()._on_barge(event,hop_end) | |
| onset=max(0.,min(2.,float(event.get('onset_s') or 0.))) | |
| samples=int(onset*16000) | |
| self.identity_audio=self.identity_audio[-samples:] if samples else np.zeros(0,np.float32) | |
| self.identity.reset_utterance() | |
| if self.teacher_stream is not None:self.teacher_stream.reset() | |
| async def _on_browser(self,msg): | |
| if msg.type==WSMsgType.BINARY: | |
| block=mic_block(msg.data) | |
| if block is not None: | |
| pcm=block[2];self.input_samples+=len(pcm);self.input_blocks+=1 | |
| now=time.monotonic() | |
| if now-self.last_input_notice>.5: | |
| self.last_input_notice=now | |
| await self.ws.send_json({'type':'input_status','blocks':self.input_blocks, | |
| 'seconds':round(self.input_samples/16000,2), | |
| 'rms_dbfs':round(20*np.log10(max(1e-7,float(np.sqrt(np.mean(pcm**2))))),1)}) | |
| if getattr(self,'calibrating',False) and msg.type==WSMsgType.BINARY: | |
| block=mic_block(msg.data) | |
| if block is not None: | |
| rid,at24,pcm=block;ref=np.zeros(len(pcm),np.float32) | |
| if rid==self.playback.rid and at24>=0: | |
| at=round(at24*16000/24000);length=max(0,min(len(pcm),self.playback.n16-at)) | |
| ref[:length]=self.playback.audio[at:at+length] | |
| self.cal_mic.append(pcm);self.cal_ref.append(ref) | |
| await super()._on_browser(msg) | |
| async def _on_command(self,data): | |
| if data.startswith('identity:'): | |
| command=data.split(':',1)[1] | |
| if command.startswith('mode:v4'): | |
| mode=command.split(':',1)[1] | |
| if mode not in ('v4','v4-long','v4-long3'):return | |
| from speaker_runtime import available | |
| if mode=='v4-long3' and 'long3' not in available(): | |
| await self.ws.send_json({'type':'warning','text':'The 3.04-s optimized study has not finished; no winner is claimed yet.'});return | |
| if self.speaker_ext is not None:await self.speaker_ext.close() | |
| from speaker_session import SpeakerSession | |
| self.current_speaker=None;self.speaker_ext=SpeakerSession(self,None if mode=='v4' else 'long3' if mode=='v4-long3' else 'long',use_diarization=mode!='v4') | |
| self.speaker_ext.start();self.identity_mode=mode;self.identity_enabled=True | |
| self._speaker_barge_sensitivity(True) | |
| await self.speaker_ext.notice();return | |
| if self.speaker_ext is not None and self.identity_mode.startswith('v4'): | |
| if command in ('on','off'): | |
| self.identity_enabled=command=='on';await self.speaker_ext.clear();self.current_speaker=None | |
| self._speaker_barge_sensitivity(self.identity_enabled) | |
| self.session.reset();await self.speaker_ext.notice();return | |
| if command=='clear': | |
| await self.speaker_ext.clear();self.current_speaker=None;await self.speaker_ext.notice();return | |
| if command.startswith('timeout:'): | |
| try: | |
| value=float(command.split(':',1)[1]) | |
| if np.isfinite(value) and 0<=value<=3600:self.speaker_ext.memory.timeout_s=value | |
| except ValueError:pass | |
| await self.speaker_ext.notice();return | |
| if command.startswith('threshold:'): | |
| try: | |
| value=float(command.split(':',1)[1]) | |
| if np.isfinite(value) and .1<=value<=.95:self.speaker_ext.memory.threshold=value | |
| except ValueError:pass | |
| await self.speaker_ext.notice();return | |
| if command.startswith('utterance_threshold:'): | |
| try: | |
| value=float(command.split(':',1)[1]) | |
| if np.isfinite(value) and .3<=value<=.9:self.speaker_ext.utterance_threshold=value | |
| except ValueError:pass | |
| await self.speaker_ext.notice();return | |
| if command.startswith('mode:') and command.split(':')[1] in ('ecapa','student'): | |
| if self.speaker_ext is not None:await self.speaker_ext.close();self.speaker_ext=None | |
| self._speaker_barge_sensitivity(False) | |
| self.current_speaker=None | |
| if data.startswith('identity:'): | |
| command=data.split(':',1)[1] | |
| if command=='on': | |
| self._reset_identity_context() | |
| if self.identity_mode=='ecapa': | |
| try:await self._teacher();self.identity_enabled=True | |
| except Exception as exc:await self.ws.send_json({'type':'warning','text':'ECAPA local weights unavailable; filter remains off ('+type(exc).__name__+').'}) | |
| else:await self.ws.send_json({'type':'warning','text':'The TYGER V2 pilot failed held-out reliability checks. Diagnostic cues only; use ECAPA to try enrollment.'}) | |
| elif command=='off':self.identity_enabled=False;self._reset_identity_context() | |
| elif command=='clear': | |
| self.identity.clear();self.identity_audio=np.zeros(0,np.float32) | |
| if self.teacher_stream is not None:self.teacher_stream.reset() | |
| if self.predictor is not None:self.predictor.reset() | |
| elif command.startswith('mode:') and command.split(':')[1] in ('ecapa','student'): | |
| self.identity_mode=command.split(':')[1];self.identity_enabled=False;self.identity.clear() | |
| self._reset_identity_context() | |
| self.identity.threshold=self.teacher_threshold if self.identity_mode=='ecapa' else self.student_threshold | |
| elif command.startswith('timeout:'): | |
| try: | |
| timeout=float(command.split(':')[1]) | |
| if np.isfinite(timeout) and 0<=timeout<=3600:self.identity.timeout_s=timeout | |
| except ValueError:pass | |
| await self.ws.send_json({'type':'identity','enabled':self.identity_enabled,'mode':self.identity_mode,**self.identity.status()}) | |
| elif data=='calibrate' and not self.speaking: | |
| self.calibrating=True;self.cal_mic=[];self.cal_ref=[];self._start_speaking() | |
| calibration_rid=self.playback.rid | |
| t=np.arange(24000*.45)/24000 | |
| probe=(.025*np.sin(2*np.pi*(250*t+1600*t*t))*np.sin(np.pi*t/.45)**2).astype(np.float32) | |
| await self._on_reply_audio(probe.tobytes()) | |
| async def finish_calibration(): | |
| await asyncio.sleep(2.5) | |
| if not self.calibrating or self.playback.rid!=calibration_rid:return | |
| self.calibrating=False | |
| report=estimate_echo_delay(np.concatenate(self.cal_mic) if self.cal_mic else [],np.concatenate(self.cal_ref) if self.cal_ref else []) | |
| self.cal_mic=[];self.cal_ref=[] | |
| if not self.ws.closed: | |
| await self.ws.send_json({'type':'calibration',**report}) | |
| await self._on_done(b'{}') | |
| self.calibration_task=asyncio.create_task(finish_calibration()) | |
| elif data=='interrupt' and self.speaking: | |
| await self._on_barge({'onset_s':0.,'yield_prob':1.},self.hop_pos) | |
| elif data.startswith('text:') and not self.speaking and not self.halting: | |
| text=data[5:].strip()[:2000] | |
| if text:self.current_speaker=None;await self._send_turn(text) | |
| elif data.startswith('text:'):await self.ws.send_json({'type':'busy','text':'Please stop or finish this reply before sending another typed turn.'}) | |
| else:await super()._on_command(data) | |
| async def _finish_turn(self,barged): | |
| offset=self.playback.offset_ms() | |
| await super()._finish_turn(barged) | |
| if not barged: | |
| self.identity_audio=np.zeros(0,np.float32);self.identity.reset_utterance() | |
| if self.predictor is not None:self.predictor.reset() | |
| if self.teacher_stream is not None:self.teacher_stream.reset() | |
| await self.ws.send_json({'type':'sync','arrival_order_error_ms':offset, | |
| 'method':'capture timestamps + actual scheduled PCM sample positions', | |
| 'hardware_accuracy':'not independently measured; inspect device latency availability'}) | |
| async def startup(app): | |
| import torch | |
| torch.set_num_threads(4) | |
| bridge=MouthBridge();app['bridge']=bridge;app['tasks']=[] | |
| app['mouth_server']=await asyncio.start_server(bridge.accept,'127.0.0.1',8795) | |
| cfg={'server':{'max_sessions':1,'max_session_s':int(os.environ.get('ROBO_MAX_SESSION_S','1800')),'mouth_host':'127.0.0.1','mouth_port':8795},'mouth':{}} | |
| app['cfg']=cfg | |
| async def load(): | |
| try: | |
| block={'plugin':'tyger','model':str(paths.BUNDLE),'ep_thr':.915,'ep_deb':2, | |
| 'barge_thr':.5,'barge_deb':2,'bc_thr':.4,'bc_deb':2,'cpu_threads':4} | |
| STATE['ears']=await asyncio.to_thread(Tyger,block,PluginContext(None,cfg,'robo-ears')) | |
| attach_feature_tap(STATE['ears']) | |
| STATE.update(ready=True,phase='Ready: Small RL + Nemotron 3.5 + TYGER') | |
| app['tasks'].append(asyncio.create_task(bridge.prepare_ack())) | |
| except Exception as exc: | |
| log.exception('EARS startup failed');STATE.update(error=type(exc).__name__+': '+str(exc),phase='EARS error') | |
| app['load_task']=asyncio.create_task(load()) | |
| async def cleanup(app): | |
| for task in app['tasks']:task.cancel() | |
| app['mouth_server'].close();await app['mouth_server'].wait_closed() | |
| async def state(request): | |
| import torch | |
| trained=(ROOT/'speaker_v2/artifacts/best.pt').exists() | |
| try: | |
| from speaker_teacher import resolve_model_root | |
| resolve_model_root();teacher=True | |
| except Exception:teacher=False | |
| from speaker_runtime import available | |
| students=available() | |
| return web.json_response({k:v for k,v in STATE.items() if k!='ears'}|{ | |
| 'tts':'Humaneness Voice Small 1.1 Alpha RL r128 v1','tts_shared':True, | |
| 'privacy':'Camera inference is local in your browser. Mic audio goes to this server; transcripts go to the configured Luna API. No mic recordings are saved by Robo.', | |
| 'identity_model':'V4 layer-mix 320-ms + session enrollment; experimental, not authentication' if students else 'legacy ECAPA baseline', | |
| 'identity_available':bool(students) or teacher,'identity_student_available':bool(students), | |
| 'identity_models':students,'identity_quiet_seconds':1., | |
| 'identity_teacher_available':teacher,'identity_default_enabled':bool(students), | |
| 'tf32_matmul':bool(torch.backends.cuda.matmul.allow_tf32),'tf32_cudnn':bool(torch.backends.cudnn.allow_tf32)}) | |
| async def socket(request): | |
| synthetic_diagnostics=request.query.get('synthetic_diagnostics')=='1' | |
| if synthetic_diagnostics:local_only(request) | |
| if not STATE['ready']:raise web.HTTPServiceUnavailable(text=STATE['error'] or STATE['phase']) | |
| ears=STATE['ears'];ears.browser_waiting=getattr(ears,'browser_waiting',0)+1;sess=None | |
| try: | |
| for _ in range(100): | |
| sess=ears.open_session(ears.defaults) | |
| if sess is not None:break | |
| await asyncio.sleep(.02) | |
| finally:ears.browser_waiting-=1 | |
| if sess is None:raise web.HTTPTooManyRequests(text='One microphone session at a time on this pilot.') | |
| ws=web.WebSocketResponse(max_msg_size=65536,heartbeat=30) | |
| try: | |
| await ws.prepare(request) | |
| conv=RoboConversation(ws,ears,sess,request.app['cfg'],voices=['emolia_c1699'], | |
| default_voice='emolia_c1699',labels={'emolia_c1699':'Mira · fixed reference'}) | |
| conv.synthetic_diagnostics=synthetic_diagnostics | |
| await conv.run() | |
| finally:ears.close(sess) | |
| return ws | |
| async def index(request):return web.FileResponse(ROOT/'static/index.html') | |
| async def desk_robot(request): | |
| # Share the actual conversation UI, not a second diverging chat client. | |
| html=(ROOT/'static/index.html').read_text() | |
| html=html.replace('<body>','<body data-robot-view="desk3d">') | |
| html=html.replace('<title>Robby · a little more human</title>','<title>Robby · 3D desk robot prototype</title>') | |
| html=html.replace('href="/desk-robot/">3D desk robot ↗','href="/">Original avatar ↗') | |
| html=html.replace('A FACE. A VOICE. A CONVERSATION.','TWO AXES. FORTY EXPRESSIONS. ONE LITTLE ROBOT.') | |
| html=html.replace('<h1>A little company.<br><span>A little expression.</span></h1>', | |
| '<h1>A little desk companion.<br><span>Made to turn towards you.</span></h1>') | |
| html=html.replace('id="avatar" class="avatar"','id="avatar" class="avatar desk-avatar"') | |
| html=html.replace('smoothly varying brows, eyelids, gaze, mouth, head and arms.', | |
| 'smoothly varying brows, eyelids, gaze and mouth. This construction has pan and tilt only, no arms or roll axis.') | |
| html=html.replace('Show the robot’s 101 animation anchors','Show display anchors and mechanical axes') | |
| return web.Response(text=html,content_type='text/html',headers={'Cache-Control':'no-store'}) | |
| async def validate(request): | |
| local_only(request) | |
| if not STATE['ready'] or STATE['ears'].active():raise web.HTTPConflict(text='EARS must be ready and idle') | |
| from sync_validation import model_checks | |
| return web.json_response(await model_checks(STATE['ears'])) | |
| async def features(request):return await extract(request,STATE['ears']) | |
| async def context_features(request):return await extract_context(request,STATE['ears']) | |
| async def layer_features(request):return await extract_layers(request,STATE['ears']) | |
| async def live_speaker_features(request): | |
| from speaker_live.research import extract_predictions | |
| return await extract_predictions(request,STATE['ears']) | |
| async def asr_check(request):return await transcribe_check(request,STATE['ears']) | |
| async def v4_status(request): | |
| path=ROOT/'speaker_v4/progress.json' | |
| return web.json_response(json.loads(path.read_text()) if path.exists() else {'phase':'Preparing'},headers={'Cache-Control':'no-store'}) | |
| async def v4_report(request): | |
| path=Path.home()/'.cache/robo-v4/report.html' | |
| return web.FileResponse(path) if path.exists() else web.Response(text='Study preparing; please reload shortly.') | |
| async def v5_status(request): | |
| path=ROOT/'speaker_v5/progress.json' | |
| return web.json_response(json.loads(path.read_text()) if path.exists() else {'phase':'Queued'},headers={'Cache-Control':'no-store'}) | |
| async def v5_report(request): | |
| path=Path.home()/'.cache/robo-v5/report.html' | |
| return web.FileResponse(path) if path.exists() else web.Response(text='Context study preparing; please reload shortly.') | |
| async def training_status(request): | |
| path=ROOT/'speaker_v3/progress.json' | |
| data=json.loads(path.read_text()) if path.exists() else {'phase':'Not started'} | |
| data.pop('results_path',None) | |
| return web.json_response(data,headers={'Cache-Control':'no-store'}) | |
| async def training_page(request):return web.FileResponse(ROOT/'static/speaker-training.html') | |
| async def large_training_report(request): | |
| path=Path(os.environ.get('ROBO_V3_WORK',str(Path.home()/'.cache/robo-v3')))/'models/report.html' | |
| if not path.exists():return web.HTTPFound('/speaker-training/') | |
| return web.FileResponse(path) | |
| async def expressions(request):return web.FileResponse(ROOT/'static/expressions.html') | |
| async def speaker_report(request): | |
| variant=request.match_info.get('variant') | |
| path=ROOT/'speaker_v2'/variant/'report.html' if variant else ROOT/'speaker_v2/report.html' | |
| if not path.exists():raise web.HTTPServiceUnavailable(text='Pilot report is still being generated') | |
| return web.FileResponse(path) | |
| async def release_download(request): | |
| path=ROOT/'speaker_v2/release/tyger-v2-public-pilot.zip' | |
| if not path.exists():raise web.HTTPNotFound() | |
| return web.FileResponse(path,headers={'Content-Disposition':'attachment; filename="tyger-v2-public-pilot.zip"'}) | |
| async def source_download(request): | |
| path=ROOT/'releases/robby-source.zip' | |
| if not path.exists():raise web.HTTPNotFound() | |
| return web.FileResponse(path,headers={'Content-Disposition':'attachment; filename="robby-source.zip"'}) | |
| def main(): | |
| app=web.Application(client_max_size=2*1024*1024) | |
| app.router.add_get('/',index);app.router.add_get('/robo/',index) | |
| app.router.add_get('/desk-robot/',desk_robot) | |
| app.router.add_get('/desk-robot',lambda request:web.HTTPFound('/desk-robot/')) | |
| app.router.add_get('/api/state',state);app.router.add_get('/ws',socket) | |
| app.router.add_post('/internal/sync-check',validate) | |
| app.router.add_post('/internal/features',features) | |
| app.router.add_post('/internal/context-features',context_features) | |
| app.router.add_post('/internal/layer-features',layer_features) | |
| app.router.add_post('/internal/speaker-predictions',live_speaker_features) | |
| app.router.add_post('/internal/transcribe',asr_check) | |
| app.router.add_get('/api/speaker-v4',v4_status) | |
| app.router.add_get('/speaker-training/320ms-study/',v4_report) | |
| app.router.add_get('/api/speaker-v5',v5_status) | |
| app.router.add_get('/speaker-training/context-study/',v5_report) | |
| app.router.add_get('/api/speaker-training',training_status) | |
| app.router.add_get('/speaker-training/',training_page) | |
| app.router.add_get('/speaker-training/report.html',large_training_report) | |
| app.router.add_get('/expressions/',expressions) | |
| app.router.add_get('/speaker-study/',speaker_report) | |
| app.router.add_get('/speaker-study/{variant:results|results_centered|results_v2_raw|results_v2_centered|results_v2_gru}/report.html',speaker_report) | |
| app.router.add_get('/downloads/tyger-v2-public-pilot.zip',release_download) | |
| app.router.add_get('/downloads/robby-source.zip',source_download) | |
| app.router.add_static('/static/',ROOT/'static') | |
| app.on_startup.append(startup);app.on_cleanup.append(cleanup) | |
| logging.basicConfig(level=logging.INFO,format='%(asctime)s %(name)s %(message)s') | |
| logging.getLogger('httpx').setLevel(logging.WARNING) | |
| web.run_app(app,host='127.0.0.1',port=8793,access_log=None) | |
| if __name__=='__main__':main() | |