Skip to content

Commit f284a20

Browse files
l3utterflyCopilot
andcommitted
- implemented stop requests
- improved broken connection recovering logic Co-authored-by: Copilot <copilot@github.com>
1 parent ad151f0 commit f284a20

1 file changed

Lines changed: 61 additions & 5 deletions

File tree

src/screens/LLMServerPanel.tsx

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type LogType = "INFO" | "WARN" | "ERROR" | "RTC" | "SSE" | "SERVER";
3838

3939
interface 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

Comments
 (0)