Fix xterm clipping; move mic + Jump to Current into status bar #7
@@ -316,6 +316,7 @@ export default function TerminalView({ sessionId, active }: Props) {
|
|||||||
try { webglRef.current?.dispose(); } catch { /* may already be disposed */ }
|
try { webglRef.current?.dispose(); } catch { /* may already be disposed */ }
|
||||||
webglRef.current = null;
|
webglRef.current = null;
|
||||||
term.dispose();
|
term.dispose();
|
||||||
|
termRef.current = null;
|
||||||
};
|
};
|
||||||
}, [sessionId]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [sessionId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
@@ -398,6 +399,21 @@ export default function TerminalView({ sessionId, active }: Props) {
|
|||||||
setScrollActiveToBottom(handleScrollToBottom);
|
setScrollActiveToBottom(handleScrollToBottom);
|
||||||
}, [active, isAtBottom, handleScrollToBottom, setTerminalAtBottom, setScrollActiveToBottom]);
|
}, [active, isAtBottom, handleScrollToBottom, setTerminalAtBottom, setScrollActiveToBottom]);
|
||||||
|
|
||||||
|
// On unmount, if this was the active terminal, clear the status-bar scroll
|
||||||
|
// state so it doesn't point at a disposed terminal. (Tab switches don't
|
||||||
|
// unmount — the deactivating terminal stays mounted but hidden — so this
|
||||||
|
// only fires when the active session is actually closed.)
|
||||||
|
const activeRef = useRef(active);
|
||||||
|
activeRef.current = active;
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (activeRef.current) {
|
||||||
|
setTerminalAtBottom(true);
|
||||||
|
setScrollActiveToBottom(() => {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [setTerminalAtBottom, setScrollActiveToBottom]);
|
||||||
|
|
||||||
const writeSelection = useCallback((mode: "trimmed" | "raw") => {
|
const writeSelection = useCallback((mode: "trimmed" | "raw") => {
|
||||||
const term = termRef.current;
|
const term = termRef.current;
|
||||||
if (!term) return;
|
if (!term) return;
|
||||||
@@ -466,8 +482,8 @@ export default function TerminalView({ sessionId, active }: Props) {
|
|||||||
FitAddon measures the host element it's mounted into; padding there
|
FitAddon measures the host element it's mounted into; padding there
|
||||||
causes the grid to overhang and clip the rightmost column / bottom
|
causes the grid to overhang and clip the rightmost column / bottom
|
||||||
row. The host below fills this wrapper's content box with no padding.
|
row. The host below fills this wrapper's content box with no padding.
|
||||||
Kept tight so the terminal claims as much area as possible; right side
|
Kept to a tight, even gutter so the terminal claims as much area as
|
||||||
leaves a little room beside the scrollbar. */}
|
possible while leaving a little breathing room beside the scrollbar. */}
|
||||||
<div className="w-full h-full" style={{ padding: "4px 8px 4px 8px" }}>
|
<div className="w-full h-full" style={{ padding: "4px 8px 4px 8px" }}>
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
|||||||
const streamRef = useRef<MediaStream | null>(null);
|
const streamRef = useRef<MediaStream | null>(null);
|
||||||
const workletRef = useRef<AudioWorkletNode | null>(null);
|
const workletRef = useRef<AudioWorkletNode | null>(null);
|
||||||
const chunksRef = useRef<Int16Array[]>([]);
|
const chunksRef = useRef<Int16Array[]>([]);
|
||||||
|
// Pin the transcript to the terminal that was active when recording STARTED.
|
||||||
|
// The hook is bound to the live active session, which can change mid-recording
|
||||||
|
// (the user switches tabs); without this the transcript would land in whatever
|
||||||
|
// tab is active at stop time.
|
||||||
|
const recordingSessionIdRef = useRef(sessionId);
|
||||||
|
|
||||||
const appSettings = useAppState((s) => s.appSettings);
|
const appSettings = useAppState((s) => s.appSettings);
|
||||||
const deviceId = appSettings?.default_microphone;
|
const deviceId = appSettings?.default_microphone;
|
||||||
@@ -22,6 +27,7 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
|||||||
setState("recording");
|
setState("recording");
|
||||||
setError(null);
|
setError(null);
|
||||||
chunksRef.current = [];
|
chunksRef.current = [];
|
||||||
|
recordingSessionIdRef.current = sessionId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const audioConstraints: MediaTrackConstraints = {
|
const audioConstraints: MediaTrackConstraints = {
|
||||||
@@ -57,7 +63,7 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
|||||||
setError(msg);
|
setError(msg);
|
||||||
setState("error");
|
setState("error");
|
||||||
}
|
}
|
||||||
}, [state, deviceId]);
|
}, [state, deviceId, sessionId]);
|
||||||
|
|
||||||
const stopRecording = useCallback(async () => {
|
const stopRecording = useCallback(async () => {
|
||||||
if (state !== "recording") return;
|
if (state !== "recording") return;
|
||||||
@@ -102,7 +108,7 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
|||||||
|
|
||||||
const text = await commands.transcribeAudio(audioData);
|
const text = await commands.transcribeAudio(audioData);
|
||||||
if (text) {
|
if (text) {
|
||||||
await sendInput(sessionId, text);
|
await sendInput(recordingSessionIdRef.current, text);
|
||||||
}
|
}
|
||||||
setState("idle");
|
setState("idle");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -112,7 +118,7 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
|||||||
// Reset to idle after a brief delay so the UI shows the error
|
// Reset to idle after a brief delay so the UI shows the error
|
||||||
setTimeout(() => setState("idle"), 3000);
|
setTimeout(() => setState("idle"), 3000);
|
||||||
}
|
}
|
||||||
}, [state, sessionId, sendInput]);
|
}, [state, sendInput]);
|
||||||
|
|
||||||
const cancelRecording = useCallback(async () => {
|
const cancelRecording = useCallback(async () => {
|
||||||
workletRef.current?.disconnect();
|
workletRef.current?.disconnect();
|
||||||
|
|||||||
Reference in New Issue
Block a user