mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
fix(input): image paste and dictation land in the session they started in
Both read activeSessionId at the END of an async gap, so switching tabs in between sent the input to the wrong session: - An image upload inserted its paths with sendInput(), which re-reads activeSessionId after the uploads finish. It now inserts into the session the batch was uploaded to, through the same durable queue. - Voice dictation read the target when the transcript arrived and again when the send button or the compose overlay's Send was pressed. The target is now captured in start() (via _focusedPane()), the local-echo overlay is only used when that target is the active session, and a target that closed meanwhile gets a toast instead of a 404. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -185,8 +185,12 @@ Object.assign(CodemanApp.prototype, {
|
||||
|
||||
const paths = results.filter(Boolean);
|
||||
if (paths.length > 0 && options.insert !== false) {
|
||||
// Insert all paths in one shot, space-separated, in selection order.
|
||||
await this.sendInput(paths.join(' '));
|
||||
// Insert all paths in one shot, space-separated, in selection order, into
|
||||
// the session the batch was uploaded TO. Not sendInput(): it re-reads
|
||||
// activeSessionId, and after the awaits above that is whatever tab the
|
||||
// user switched to mid-upload, so the paths landed in the wrong session.
|
||||
// Same delivery sendInput() uses (durable queue, useMux for the POST path).
|
||||
this._sendInputAsync(sessionId, paths.join(' '), { useMux: true });
|
||||
}
|
||||
|
||||
// Final status: successes, plus any failures / cap so nothing is silent.
|
||||
|
||||
@@ -554,6 +554,11 @@ const VoiceInput = {
|
||||
_analyserSource: null, // MediaStreamSource for level meter
|
||||
_audioContext: null, // AudioContext for level meter
|
||||
_levelAnimFrame: null, // rAF handle for level meter
|
||||
// The session dictation was started FOR, captured in start(). Transcripts
|
||||
// arrive seconds later and the green send button / compose overlay can be
|
||||
// used later still; reading app.activeSessionId at that point sent the text
|
||||
// to whatever tab the user had switched to in the meantime.
|
||||
_targetSessionId: null,
|
||||
|
||||
init() {
|
||||
this._initRecognition();
|
||||
@@ -663,10 +668,12 @@ const VoiceInput = {
|
||||
|
||||
start() {
|
||||
if (this.isRecording) return;
|
||||
if (!app.activeSessionId) {
|
||||
const target = app._focusedPane?.()?.sessionId || app.activeSessionId;
|
||||
if (!target) {
|
||||
app.showToast('No active session', 'warning');
|
||||
return;
|
||||
}
|
||||
this._targetSessionId = target;
|
||||
this._retryCount = 0;
|
||||
|
||||
const provider = this._resolveProvider();
|
||||
@@ -959,8 +966,31 @@ const VoiceInput = {
|
||||
this.stop();
|
||||
},
|
||||
|
||||
/** The session this dictation belongs to (see _targetSessionId). */
|
||||
_targetSession() {
|
||||
return this._targetSessionId || app.activeSessionId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send text to the dictation's own session. The active session keeps going
|
||||
* through app.sendInput() exactly as before; any other session goes straight
|
||||
* to the durable queue with the same useMux flag sendInput() passes.
|
||||
*/
|
||||
_sendToTarget(target, text) {
|
||||
if (target === app.activeSessionId) return app.sendInput(text);
|
||||
// Closed while dictating: say so instead of queueing text for a session
|
||||
// that will only answer 404 (and never typing it into some other tab).
|
||||
if (app.sessions && !app.sessions.has(target)) {
|
||||
app.showToast?.('That session has closed; dictation not sent', 'warning');
|
||||
return Promise.resolve();
|
||||
}
|
||||
app._sendInputAsync(target, text, { useMux: true });
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
_insertText(text) {
|
||||
if (!app.activeSessionId || !text.trim()) return;
|
||||
const target = this._targetSession();
|
||||
if (!target || !text.trim()) return;
|
||||
const trimmed = text.trim();
|
||||
const mode = this._getDeepgramConfig().insertMode || 'direct';
|
||||
|
||||
@@ -975,14 +1005,17 @@ const VoiceInput = {
|
||||
this._showComposeOverlay(trimmed);
|
||||
}
|
||||
} else {
|
||||
// Direct mode: inject into local echo overlay if available, else send to PTY
|
||||
if (app._localEchoEnabled && app._localEchoOverlay) {
|
||||
// Direct mode: inject into local echo overlay if available, else send to PTY.
|
||||
// The overlay belongs to the ACTIVE session's terminal, so text dictated
|
||||
// for any other session must not be typed into it.
|
||||
const isActive = target === app.activeSessionId;
|
||||
if (isActive && app._localEchoEnabled && app._localEchoOverlay) {
|
||||
app._localEchoOverlay.appendText(trimmed);
|
||||
} else {
|
||||
app.sendInput(trimmed).catch(() => {});
|
||||
this._sendToTarget(target, trimmed).catch(() => {});
|
||||
}
|
||||
this._showVoiceSendBtn();
|
||||
setTimeout(() => { if (app.terminal) app.terminal.focus(); }, 150);
|
||||
setTimeout(() => { if (isActive && app.terminal) app.terminal.focus(); }, 150);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1008,10 +1041,15 @@ const VoiceInput = {
|
||||
|
||||
// Click handler
|
||||
this._voiceSendHandler = () => {
|
||||
if (!app.activeSessionId) return;
|
||||
const target = this._targetSession();
|
||||
if (!target) return;
|
||||
// Simulate Enter key: if local echo is active, flush its buffer + send \r;
|
||||
// otherwise just send \r directly to the PTY
|
||||
if (app._localEchoEnabled && app._localEchoOverlay) {
|
||||
// otherwise just send \r directly to the PTY. Both the overlay and the
|
||||
// predictions belong to the ACTIVE session's terminal, so a dictation
|
||||
// for another session just sends its Enter there.
|
||||
if (target !== app.activeSessionId) {
|
||||
this._sendToTarget(target, '\r').catch(() => {});
|
||||
} else if (app._localEchoEnabled && app._localEchoOverlay) {
|
||||
const text = app._localEchoOverlay.pendingText || '';
|
||||
app._localEchoOverlay.clear();
|
||||
app._localEchoOverlay.suppressBufferDetection();
|
||||
@@ -1065,7 +1103,7 @@ const VoiceInput = {
|
||||
const send = () => {
|
||||
const val = textarea.value.trim();
|
||||
overlay.remove();
|
||||
if (val) app.sendInput(val + '\r').catch(() => {});
|
||||
if (val) this._sendToTarget(this._targetSession(), val + '\r').catch(() => {});
|
||||
};
|
||||
const cancel = () => overlay.remove();
|
||||
const newInput = () => {
|
||||
|
||||
Reference in New Issue
Block a user