@@ -38,7 +38,7 @@ type LogType = "INFO" | "WARN" | "ERROR" | "RTC" | "SSE" | "SERVER";
3838
3939interface LaylaServerTransportMessage {
4040 sessionId : string ;
41- type : "start" | "chunk" | "end" ;
41+ type : "start" | "chunk" | "end" | "cmd" ;
4242 payload : string ;
4343}
4444
@@ -344,8 +344,9 @@ const LlmServerPanel: React.FC<{
344344 const lastAnswerRef = useRef < string | null > ( null ) ;
345345 const creatingRtcOfferGuardRef = useRef ( false ) ; // to prevent concurrent offer creations
346346
347- // ── Buffered request ──
347+ // ── OpenAI proxy ──
348348 const bufferedRequestRef = useRef ( "" ) ;
349+ const streamAbortControllerRef = useRef < AbortController | null > ( null ) ;
349350
350351 // ── Model info ──
351352 const [ modelName , setModelName ] = useState ( "No model loaded" ) ;
@@ -371,12 +372,20 @@ const LlmServerPanel: React.FC<{
371372 try {
372373 addLog ( "SSE" , `Streaming request to local server…` ) ;
373374
375+ // re-create abort controller for this stream
376+ if ( streamAbortControllerRef . current ) {
377+ streamAbortControllerRef . current . abort ( ) ;
378+ }
379+ streamAbortControllerRef . current = new AbortController ( ) ;
380+
374381 const response = await fetch (
375- localServerUrlRef . current || USER_SETTING_DEFAULTS [ UserSettingKey . LOCAL_SERVER_URL ] ,
382+ localServerUrlRef . current ||
383+ USER_SETTING_DEFAULTS [ UserSettingKey . LOCAL_SERVER_URL ] ,
376384 {
377385 method : "POST" ,
378386 headers : { "Content-Type" : "application/json" } ,
379387 body : requestBody ,
388+ signal : streamAbortControllerRef . current . signal ,
380389 } ,
381390 ) ;
382391
@@ -420,6 +429,16 @@ const LlmServerPanel: React.FC<{
420429 }
421430 } , [ ] ) ;
422431
432+ const handleCommand = useCallback ( ( cmd : string ) => {
433+ addLog ( "INFO" , `Received command: ${ cmd } ` ) ;
434+ if ( cmd === "stop" ) {
435+ streamAbortControllerRef . current ?. abort ( ) ;
436+ addLog ( "INFO" , "Stream aborted by command" ) ;
437+ } else {
438+ addLog ( "WARN" , `Unknown command: ${ cmd } ` ) ;
439+ }
440+ } , [ ] ) ;
441+
423442 // ── WebRTC (browser native) ──
424443 const createRtcOffer = async ( ) => {
425444 if ( creatingRtcOfferGuardRef . current ) return ;
@@ -465,8 +484,9 @@ const LlmServerPanel: React.FC<{
465484 dc . onclose = ( ) => {
466485 addLog ( "RTC" , "DataChannel CLOSED" ) ;
467486
468- // re-create offer if server is still running
469- if ( runningRef . current ) createRtcOffer ( ) ;
487+ // re-create offer if server is still running and we we not re-creating the rtc offer
488+ if ( runningRef . current && ! creatingRtcOfferGuardRef . current )
489+ createRtcOffer ( ) ;
470490 } ;
471491
472492 dc . onmessage = ( event ) => {
@@ -485,6 +505,9 @@ const LlmServerPanel: React.FC<{
485505 } else if ( chunk . type === "end" ) {
486506 // Stream the request to local llama.cpp via SSE (fetch streaming)
487507 streamToLocalServer ( bufferedRequestRef . current ) ;
508+ } else if ( chunk . type === "cmd" ) {
509+ // Handle command messages
510+ handleCommand ( chunk . payload ) ;
488511 }
489512 } catch ( e : any ) {
490513 addLog ( "ERROR" , `Failed to handle RTC message: ${ e . message } ` ) ;
@@ -608,6 +631,39 @@ const LlmServerPanel: React.FC<{
608631 addLog ( "RTC" , "Remote answer received:\n\n" + payload ) ;
609632 lastAnswerRef . current = payload ;
610633 answerReceived = true ;
634+
635+ // Wait up to 10s for the DataChannel to open, else retry
636+ if ( dc . readyState !== "open" ) {
637+ addLog ( "RTC" , "Waiting up to 10s for DataChannel to open…" ) ;
638+ const opened = await new Promise < boolean > ( ( resolve ) => {
639+ const timeout = setTimeout ( ( ) => resolve ( false ) , 10_000 ) ;
640+ const origOnOpen = dc . onopen ;
641+ dc . onopen = ( ev ) => {
642+ clearTimeout ( timeout ) ;
643+ origOnOpen ?. call ( dc , ev ) ;
644+ resolve ( true ) ;
645+ } ;
646+ } ) ;
647+
648+ if ( ! opened && runningRef . current ) {
649+ addLog (
650+ "WARN" ,
651+ "DataChannel did not open within 10s, retrying offer…" ,
652+ ) ;
653+ try {
654+ dc . close ( ) ;
655+ pc . close ( ) ;
656+ } catch ( _ ) { }
657+
658+ peerConnectionRef . current = null ;
659+ dataChannelRef . current = null ;
660+ creatingRtcOfferGuardRef . current = false ;
661+
662+ // here we call our own function recursively to completely setup a new offer etc.
663+ createRtcOffer ( ) ;
664+ return ;
665+ }
666+ }
611667 } while ( ! answerReceived && runningRef . current ) ;
612668 } catch ( e : any ) {
613669 addLog ( "ERROR" , e . message ) ;
0 commit comments