aicodeman 1.31.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.8df358cd.js → app.a792bf71.js} +2 -2
- package/dist/web/public/app.a792bf71.js.br +0 -0
- package/dist/web/public/{app.8df358cd.js.gz → app.a792bf71.js.gz} +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.85ed12f3.js → constants.b26748e9.js} +49 -0
- package/dist/web/public/constants.b26748e9.js.br +0 -0
- package/dist/web/public/constants.b26748e9.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.d22be5c4.js +1 -0
- package/dist/web/public/i18n.d22be5c4.js.br +0 -0
- package/dist/web/public/i18n.d22be5c4.js.gz +0 -0
- package/dist/web/public/{image-input.cd4b97c4.js → image-input.a216cf9e.js} +8 -4
- package/dist/web/public/image-input.a216cf9e.js.br +0 -0
- package/dist/web/public/image-input.a216cf9e.js.gz +0 -0
- package/dist/web/public/index.html +14 -11
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.2cf04f17.js → keyboard-accessory.c1e7c7b7.js} +280 -15
- package/dist/web/public/keyboard-accessory.c1e7c7b7.js.br +0 -0
- package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
- package/dist/web/public/mobile-handlers.6f354a87.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.6caaa28a.css.gz +0 -0
- package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.28df5053.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.7df17fd3.js +88 -0
- package/dist/web/public/session-ui.7df17fd3.js.br +0 -0
- package/dist/web/public/session-ui.7df17fd3.js.gz +0 -0
- package/dist/web/public/settings-ui.df5ffb68.js +78 -0
- package/dist/web/public/settings-ui.df5ffb68.js.br +0 -0
- package/dist/web/public/settings-ui.df5ffb68.js.gz +0 -0
- package/dist/web/public/styles.74f312e5.css +1 -0
- package/dist/web/public/styles.74f312e5.css.br +0 -0
- package/dist/web/public/styles.74f312e5.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
- package/dist/web/public/terminal-split.js +804 -0
- package/dist/web/public/terminal-split.js.br +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-ui.32fbeaf6.js +2 -0
- package/dist/web/public/terminal-ui.32fbeaf6.js.br +0 -0
- package/dist/web/public/terminal-ui.32fbeaf6.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +13 -5
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.8df358cd.js.br +0 -0
- package/dist/web/public/constants.85ed12f3.js.br +0 -0
- package/dist/web/public/constants.85ed12f3.js.gz +0 -0
- package/dist/web/public/i18n.83924614.js +0 -1
- package/dist/web/public/i18n.83924614.js.br +0 -0
- package/dist/web/public/i18n.83924614.js.gz +0 -0
- package/dist/web/public/image-input.cd4b97c4.js.br +0 -0
- package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.2cf04f17.js.br +0 -0
- package/dist/web/public/keyboard-accessory.2cf04f17.js.gz +0 -0
- package/dist/web/public/session-ui.d4aea71a.js +0 -88
- package/dist/web/public/session-ui.d4aea71a.js.br +0 -0
- package/dist/web/public/session-ui.d4aea71a.js.gz +0 -0
- package/dist/web/public/settings-ui.4762f480.js +0 -78
- package/dist/web/public/settings-ui.4762f480.js.br +0 -0
- package/dist/web/public/settings-ui.4762f480.js.gz +0 -0
- package/dist/web/public/styles.e34fea04.css +0 -1
- package/dist/web/public/styles.e34fea04.css.br +0 -0
- package/dist/web/public/styles.e34fea04.css.gz +0 -0
- package/dist/web/public/terminal-ui.35812aa2.js +0 -2
- package/dist/web/public/terminal-ui.35812aa2.js.br +0 -0
- package/dist/web/public/terminal-ui.35812aa2.js.gz +0 -0
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* Defines three exports:
|
|
5
5
|
*
|
|
6
6
|
* - KeyboardAccessoryBar (singleton object) — Quick action buttons shown above the virtual
|
|
7
|
-
* keyboard on mobile: arrow up/down, /init, Tab,
|
|
7
|
+
* keyboard on mobile: arrow up/down, /init, Tab, Compose, Esc, and dismiss (the extended
|
|
8
8
|
* bar adds /clear, /compact, Shift+Tab and more). Shift+Left/Right ship in both agent
|
|
9
9
|
* layouts but are revealed only on Codex sessions (`codex-enabled` marker class on the
|
|
10
10
|
* bar, synced on every session switch), since they are Codex bindings. Tab flushes any locally-buffered
|
|
11
11
|
* prompt text to the PTY before sending \t, so completion applies to what was typed.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Agent bars expose a Compose dialog with an autocorrect-aware multiline textarea,
|
|
13
|
+
* per-session in-memory drafts and image attach. Shell bars keep the direct Paste
|
|
14
|
+
* dialog because shell input is not an agent prompt.
|
|
14
15
|
* Destructive actions (/clear, /compact, extended bar only) require double-tap confirmation (2s amber state).
|
|
15
16
|
* Commands are sent as text + Enter separately for Ink compatibility.
|
|
16
17
|
* Only initializes on touch devices (MobileDetection.isTouchDevice guard).
|
|
@@ -635,6 +636,16 @@ function applyOneShotCtrl(data) {
|
|
|
635
636
|
return { data, consumed: true };
|
|
636
637
|
}
|
|
637
638
|
|
|
639
|
+
// The composer's Send goes out as ONE bracketed-paste frame on the WebSocket
|
|
640
|
+
// input path, and ws-routes.ts drops a frame longer than MAX_INPUT_LENGTH
|
|
641
|
+
// (config/terminal-limits.ts: 64 KiB, compared in UTF-16 code units) WITHOUT
|
|
642
|
+
// an ACK, which would wedge the durable input queue. So the prompt budget is
|
|
643
|
+
// that limit minus the two markers, derived here once so the refusal in
|
|
644
|
+
// _sendComposedPrompt() and the toast that names the maximum cannot drift.
|
|
645
|
+
const COMPOSER_INPUT_FRAME_LIMIT = 64 * 1024;
|
|
646
|
+
const COMPOSER_PASTE_START = '\x1b[200~';
|
|
647
|
+
const COMPOSER_PASTE_END = '\x1b[201~';
|
|
648
|
+
|
|
638
649
|
/**
|
|
639
650
|
* KeyboardAccessoryBar - Quick action buttons shown above keyboard when typing.
|
|
640
651
|
*/
|
|
@@ -648,8 +659,15 @@ const KeyboardAccessoryBar = {
|
|
|
648
659
|
_baseMode: 'simple',
|
|
649
660
|
// One-shot Ctrl modifier (shell bar only). See handleAction('ctrl').
|
|
650
661
|
_ctrlArmed: false,
|
|
651
|
-
|
|
652
|
-
|
|
662
|
+
// Prompt drafts intentionally stay in memory: prompts routinely contain secrets,
|
|
663
|
+
// so persistence would need the same treatment as the 0600 intent store.
|
|
664
|
+
_composerDrafts: new Map(),
|
|
665
|
+
_composerUploads: new Map(),
|
|
666
|
+
_composerOverlay: null,
|
|
667
|
+
// Longest prompt Send accepts: the input frame limit minus both markers.
|
|
668
|
+
_composerMaxLength: COMPOSER_INPUT_FRAME_LIMIT - COMPOSER_PASTE_START.length - COMPOSER_PASTE_END.length,
|
|
669
|
+
|
|
670
|
+
/** HTML for simple mode: arrows, commands, Compose, Esc, dismiss */
|
|
653
671
|
_simpleButtons: `
|
|
654
672
|
<button class="accessory-btn accessory-btn-arrow" data-action="scroll-up" title="Arrow up">
|
|
655
673
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
@@ -665,10 +683,10 @@ const KeyboardAccessoryBar = {
|
|
|
665
683
|
<button class="accessory-btn" data-action="tab" title="Tab">Tab</button>
|
|
666
684
|
<button class="accessory-btn accessory-btn-codex" data-action="shift-left" title="Shift+Left (Codex: edit queued message)" aria-label="Shift+Left (Codex: edit queued message)">⇧←</button>
|
|
667
685
|
<button class="accessory-btn accessory-btn-codex" data-action="shift-right" title="Shift+Right (Codex: prompt stack back)" aria-label="Shift+Right (Codex: prompt stack back)">⇧→</button>
|
|
668
|
-
<button class="accessory-btn" data-action="
|
|
669
|
-
<svg width="
|
|
670
|
-
<path d="
|
|
671
|
-
<
|
|
686
|
+
<button class="accessory-btn accessory-btn-compose" data-action="compose" title="Compose prompt" aria-label="Compose prompt">
|
|
687
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
688
|
+
<path d="M12 20h9"/>
|
|
689
|
+
<path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/>
|
|
672
690
|
</svg>
|
|
673
691
|
</button>
|
|
674
692
|
<button class="accessory-btn accessory-btn-rmm" data-action="readmymind" title="Read My Mind: predict your next prompt">🧠</button>
|
|
@@ -740,10 +758,10 @@ const KeyboardAccessoryBar = {
|
|
|
740
758
|
<path d="M9 5l7 7-7 7"/>
|
|
741
759
|
</svg>
|
|
742
760
|
</button>
|
|
743
|
-
<button class="accessory-btn" data-action="
|
|
744
|
-
<svg width="
|
|
745
|
-
<path d="
|
|
746
|
-
<
|
|
761
|
+
<button class="accessory-btn accessory-btn-compose" data-action="compose" title="Compose prompt" aria-label="Compose prompt">
|
|
762
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
763
|
+
<path d="M12 20h9"/>
|
|
764
|
+
<path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/>
|
|
747
765
|
</svg>
|
|
748
766
|
</button>
|
|
749
767
|
<button class="accessory-btn" data-action="pick-path" title="Insert a file or folder path">📁 Path</button>
|
|
@@ -781,6 +799,7 @@ const KeyboardAccessoryBar = {
|
|
|
781
799
|
// The ⇧←/⇧→ keys are Codex bindings: same shape, gated on the active
|
|
782
800
|
// session's mode instead of a setting.
|
|
783
801
|
this.syncCodexKeys();
|
|
802
|
+
this._syncComposerDraftIndicator();
|
|
784
803
|
|
|
785
804
|
// Add click handlers — preventDefault stops event from reaching terminal
|
|
786
805
|
this.element.addEventListener('click', (e) => {
|
|
@@ -823,8 +842,13 @@ const KeyboardAccessoryBar = {
|
|
|
823
842
|
* the next one. */
|
|
824
843
|
refreshForActiveSession() {
|
|
825
844
|
this.clearCtrl();
|
|
845
|
+
const activeSessionId = typeof app !== 'undefined' ? app.activeSessionId : null;
|
|
846
|
+
if (this._composerOverlay && this._composerOverlay.dataset.sessionId !== activeSessionId) {
|
|
847
|
+
this._composerOverlay._closeComposer?.({ restoreFocus: false });
|
|
848
|
+
}
|
|
826
849
|
this._applyLayout(this._resolveMode());
|
|
827
850
|
this.syncCodexKeys();
|
|
851
|
+
this._syncComposerDraftIndicator();
|
|
828
852
|
},
|
|
829
853
|
|
|
830
854
|
/** Which layout the current state calls for. */
|
|
@@ -852,6 +876,20 @@ const KeyboardAccessoryBar = {
|
|
|
852
876
|
this.clearCtrl();
|
|
853
877
|
this.element.innerHTML =
|
|
854
878
|
mode === 'shell' ? this._shellButtons : mode === 'extended' ? this._extendedButtons : this._simpleButtons;
|
|
879
|
+
this._syncComposerDraftIndicator();
|
|
880
|
+
},
|
|
881
|
+
|
|
882
|
+
/** Show when the active session has a prompt parked in memory. The marker
|
|
883
|
+
* keeps non-Send closes visible without copying the draft back into the PTY
|
|
884
|
+
* and creating a second source of truth. */
|
|
885
|
+
_syncComposerDraftIndicator() {
|
|
886
|
+
const button = this.element?.querySelector('[data-action="compose"]');
|
|
887
|
+
if (!button) return;
|
|
888
|
+
const sessionId = typeof app !== 'undefined' ? app.activeSessionId : null;
|
|
889
|
+
const hasDraft = !!(sessionId && this._composerDrafts.get(sessionId));
|
|
890
|
+
button.classList.toggle('has-draft', hasDraft);
|
|
891
|
+
button.title = hasDraft ? 'Resume saved prompt draft' : 'Compose prompt';
|
|
892
|
+
button.setAttribute('aria-label', hasDraft ? 'Compose prompt, draft saved' : 'Compose prompt');
|
|
855
893
|
},
|
|
856
894
|
|
|
857
895
|
// ── One-shot Ctrl modifier (shell bar) ──────────────────────────────────
|
|
@@ -977,6 +1015,9 @@ const KeyboardAccessoryBar = {
|
|
|
977
1015
|
case 'paste':
|
|
978
1016
|
this.pasteFromClipboard();
|
|
979
1017
|
break;
|
|
1018
|
+
case 'compose':
|
|
1019
|
+
this.composePrompt();
|
|
1020
|
+
break;
|
|
980
1021
|
case 'pick-path':
|
|
981
1022
|
this.pickPath();
|
|
982
1023
|
break;
|
|
@@ -1130,6 +1171,230 @@ const KeyboardAccessoryBar = {
|
|
|
1130
1171
|
});
|
|
1131
1172
|
},
|
|
1132
1173
|
|
|
1174
|
+
/** Move the whole editable terminal prompt into the composer. Pending text
|
|
1175
|
+
* exists only in the overlay; flushed text already reached the PTY, so erase
|
|
1176
|
+
* that prefix before making the textarea authoritative. */
|
|
1177
|
+
_takePendingLocalEcho(sessionId) {
|
|
1178
|
+
if (!app._localEchoEnabled || !app._localEchoOverlay) return '';
|
|
1179
|
+
const pending = app._localEchoOverlay.pendingText || '';
|
|
1180
|
+
const overlayFlushed = app._localEchoOverlay.getFlushed?.() || {};
|
|
1181
|
+
const flushedText = overlayFlushed.text || app._flushedTexts?.get(sessionId) || '';
|
|
1182
|
+
const flushedLength = Array.from(flushedText).length;
|
|
1183
|
+
app._localEchoOverlay.clear();
|
|
1184
|
+
app._localEchoOverlay.suppressBufferDetection?.();
|
|
1185
|
+
app._flushedOffsets?.delete(sessionId);
|
|
1186
|
+
app._flushedTexts?.delete(sessionId);
|
|
1187
|
+
if (flushedLength > 0) {
|
|
1188
|
+
app._sendInputAsync(sessionId, '\x7f'.repeat(flushedLength), { useMux: true });
|
|
1189
|
+
}
|
|
1190
|
+
return flushedText + pending;
|
|
1191
|
+
},
|
|
1192
|
+
|
|
1193
|
+
/** Insert text at the textarea selection, adding one separating space when
|
|
1194
|
+
* an attachment path would otherwise run into neighboring prompt text. */
|
|
1195
|
+
_insertComposerText(textarea, text) {
|
|
1196
|
+
if (!textarea || !text) return;
|
|
1197
|
+
const start = Number.isInteger(textarea.selectionStart) ? textarea.selectionStart : textarea.value.length;
|
|
1198
|
+
const end = Number.isInteger(textarea.selectionEnd) ? textarea.selectionEnd : start;
|
|
1199
|
+
const before = textarea.value.slice(0, start);
|
|
1200
|
+
const after = textarea.value.slice(end);
|
|
1201
|
+
const prefix = before && !/\s$/.test(before) ? ' ' : '';
|
|
1202
|
+
const suffix = after && !/^\s/.test(after) ? ' ' : '';
|
|
1203
|
+
const inserted = `${prefix}${text}${suffix}`;
|
|
1204
|
+
textarea.setRangeText(inserted, start, end, 'end');
|
|
1205
|
+
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1206
|
+
},
|
|
1207
|
+
|
|
1208
|
+
/** Deliver one complete prompt as an explicit bracketed paste. xterm loses
|
|
1209
|
+
* its DECSET 2004 mirror after terminal replay, even though the CLI still
|
|
1210
|
+
* expects bracketed input, so build the byte-identical sequence directly on
|
|
1211
|
+
* the durable session-bound path. Enter stays a separate delayed write
|
|
1212
|
+
* because Codex drops keys sharing a PTY read with a bracketed paste. */
|
|
1213
|
+
_sendComposedPrompt(sessionId, text) {
|
|
1214
|
+
if (!sessionId || !text || typeof app._sendInputAsync !== 'function') return false;
|
|
1215
|
+
app._predictiveEcho?.clearPredictions();
|
|
1216
|
+
// Match xterm's prepareTextForTerminal(): CR keeps embedded newlines inside
|
|
1217
|
+
// the single-line input transport and is what terminal.paste() emitted.
|
|
1218
|
+
const pasteText = text.replace(/\r?\n/g, '\r');
|
|
1219
|
+
if (pasteText.length > this._composerMaxLength) {
|
|
1220
|
+
app.showToast?.(`Prompt is too long to send (maximum ${this._composerMaxLength.toLocaleString()} characters)`, 'error');
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
app._sendInputAsync(sessionId, `${COMPOSER_PASTE_START}${pasteText}${COMPOSER_PASTE_END}`);
|
|
1224
|
+
setTimeout(() => app._sendInputAsync(sessionId, '\r', { useMux: true }), 120);
|
|
1225
|
+
return true;
|
|
1226
|
+
},
|
|
1227
|
+
|
|
1228
|
+
/** Forget a draft when its target session no longer exists. */
|
|
1229
|
+
discardComposerDraft(sessionId) {
|
|
1230
|
+
this._composerDrafts.delete(sessionId);
|
|
1231
|
+
this._composerUploads.delete(sessionId);
|
|
1232
|
+
this._syncComposerDraftIndicator();
|
|
1233
|
+
if (this._composerOverlay?.dataset.sessionId === sessionId) {
|
|
1234
|
+
this._composerOverlay._closeComposer?.({ preserveDraft: false });
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
|
|
1238
|
+
/** Open the manual agent prompt composer. Enter remains a newline; only the
|
|
1239
|
+
* Send button submits. Cancel/backdrop/Escape preserve the per-session draft. */
|
|
1240
|
+
composePrompt() {
|
|
1241
|
+
if (typeof app === 'undefined' || !app.activeSessionId) return;
|
|
1242
|
+
if (this._isShellSession()) {
|
|
1243
|
+
this.pasteFromClipboard();
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const sessionId = app.activeSessionId;
|
|
1248
|
+
const pending = this._takePendingLocalEcho(sessionId);
|
|
1249
|
+
const saved = this._composerDrafts.get(sessionId) || '';
|
|
1250
|
+
const initial = saved + pending;
|
|
1251
|
+
|
|
1252
|
+
this._composerOverlay?._closeComposer?.({ restoreFocus: false });
|
|
1253
|
+
const overlay = document.createElement('div');
|
|
1254
|
+
overlay.className = 'paste-overlay prompt-composer-overlay';
|
|
1255
|
+
overlay.dataset.sessionId = sessionId;
|
|
1256
|
+
overlay.setAttribute('role', 'dialog');
|
|
1257
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
1258
|
+
overlay.setAttribute('aria-label', 'Compose prompt');
|
|
1259
|
+
overlay.innerHTML = `
|
|
1260
|
+
<div class="paste-dialog prompt-composer-dialog">
|
|
1261
|
+
<div class="prompt-composer-header">
|
|
1262
|
+
<strong>Compose prompt</strong>
|
|
1263
|
+
<span>Enter adds a new line</span>
|
|
1264
|
+
</div>
|
|
1265
|
+
<textarea class="paste-textarea prompt-composer-textarea" aria-label="Prompt" placeholder="Write your prompt…" autocorrect="on" autocapitalize="sentences" spellcheck="true"></textarea>
|
|
1266
|
+
<div class="paste-actions prompt-composer-actions">
|
|
1267
|
+
<button type="button" class="prompt-composer-terminal">Use terminal keyboard</button>
|
|
1268
|
+
<button type="button" class="paste-image">🖼 Image</button>
|
|
1269
|
+
<button type="button" class="paste-cancel">Cancel</button>
|
|
1270
|
+
<button type="button" class="paste-send">Send</button>
|
|
1271
|
+
</div>
|
|
1272
|
+
<input type="file" class="paste-file-input" accept="image/*" multiple hidden>
|
|
1273
|
+
</div>
|
|
1274
|
+
`;
|
|
1275
|
+
|
|
1276
|
+
this._composerOverlay = overlay;
|
|
1277
|
+
const textarea = overlay.querySelector('.prompt-composer-textarea');
|
|
1278
|
+
// i18n.js skips <textarea> subtrees (what is typed there is user content),
|
|
1279
|
+
// so the placeholder and label are translated here, when the dialog is built.
|
|
1280
|
+
const i18n = typeof window !== 'undefined' ? window.CodemanI18n : undefined;
|
|
1281
|
+
if (typeof i18n?.t === 'function') {
|
|
1282
|
+
textarea.placeholder = i18n.t('Write your prompt…');
|
|
1283
|
+
textarea.setAttribute('aria-label', i18n.t('Prompt'));
|
|
1284
|
+
}
|
|
1285
|
+
const fileInput = overlay.querySelector('.paste-file-input');
|
|
1286
|
+
const imageButton = overlay.querySelector('.paste-image');
|
|
1287
|
+
const sendButton = overlay.querySelector('.paste-send');
|
|
1288
|
+
const focusTrap = new FocusTrap(overlay);
|
|
1289
|
+
textarea.value = initial;
|
|
1290
|
+
if (initial) this._composerDrafts.set(sessionId, initial);
|
|
1291
|
+
this._syncComposerDraftIndicator();
|
|
1292
|
+
const initialUploads = this._composerUploads.get(sessionId) || 0;
|
|
1293
|
+
imageButton.disabled = initialUploads > 0;
|
|
1294
|
+
sendButton.disabled = initialUploads > 0;
|
|
1295
|
+
if (initialUploads > 0) imageButton.textContent = 'Uploading…';
|
|
1296
|
+
|
|
1297
|
+
const saveDraft = () => {
|
|
1298
|
+
if (textarea.value) this._composerDrafts.set(sessionId, textarea.value);
|
|
1299
|
+
else this._composerDrafts.delete(sessionId);
|
|
1300
|
+
this._syncComposerDraftIndicator();
|
|
1301
|
+
};
|
|
1302
|
+
const close = ({ focusTerminal = false, preserveDraft = true, restoreFocus = true } = {}) => {
|
|
1303
|
+
if (preserveDraft) saveDraft();
|
|
1304
|
+
focusTrap.deactivate({ restoreFocus });
|
|
1305
|
+
overlay.remove();
|
|
1306
|
+
if (this._composerOverlay === overlay) this._composerOverlay = null;
|
|
1307
|
+
if (focusTerminal) app.terminal?.focus();
|
|
1308
|
+
};
|
|
1309
|
+
overlay._closeComposer = close;
|
|
1310
|
+
const send = () => {
|
|
1311
|
+
// Whitespace-only counts as empty (it would submit blank lines), but the
|
|
1312
|
+
// text goes out untrimmed so deliberate leading or trailing lines survive.
|
|
1313
|
+
const text = textarea.value;
|
|
1314
|
+
if (!text.trim() || !this._sendComposedPrompt(sessionId, text)) return;
|
|
1315
|
+
app._echoPassthroughSessions?.delete(sessionId);
|
|
1316
|
+
this._composerDrafts.delete(sessionId);
|
|
1317
|
+
this._syncComposerDraftIndicator();
|
|
1318
|
+
close({ preserveDraft: false });
|
|
1319
|
+
};
|
|
1320
|
+
const handleImages = async (files) => {
|
|
1321
|
+
const images = Array.from(files || []).filter((file) => file.type.startsWith('image/'));
|
|
1322
|
+
if (images.length === 0 || typeof app._uploadAndInsertImages !== 'function') return;
|
|
1323
|
+
saveDraft();
|
|
1324
|
+
this._composerUploads.set(sessionId, (this._composerUploads.get(sessionId) || 0) + 1);
|
|
1325
|
+
const syncUploadUi = () => {
|
|
1326
|
+
const currentOverlay =
|
|
1327
|
+
this._composerOverlay?.isConnected && this._composerOverlay.dataset.sessionId === sessionId
|
|
1328
|
+
? this._composerOverlay
|
|
1329
|
+
: null;
|
|
1330
|
+
const count = this._composerUploads.get(sessionId) || 0;
|
|
1331
|
+
const currentImageButton = currentOverlay?.querySelector('.paste-image');
|
|
1332
|
+
const currentSendButton = currentOverlay?.querySelector('.paste-send');
|
|
1333
|
+
if (currentImageButton) {
|
|
1334
|
+
currentImageButton.disabled = count > 0;
|
|
1335
|
+
currentImageButton.textContent = count > 0 ? 'Uploading…' : '🖼 Image';
|
|
1336
|
+
}
|
|
1337
|
+
if (currentSendButton) currentSendButton.disabled = count > 0;
|
|
1338
|
+
};
|
|
1339
|
+
syncUploadUi();
|
|
1340
|
+
try {
|
|
1341
|
+
const paths = await app._uploadAndInsertImages(images, { insert: false });
|
|
1342
|
+
if (paths?.length) {
|
|
1343
|
+
const currentOverlay =
|
|
1344
|
+
this._composerOverlay?.isConnected && this._composerOverlay.dataset.sessionId === sessionId
|
|
1345
|
+
? this._composerOverlay
|
|
1346
|
+
: null;
|
|
1347
|
+
const currentTextarea = currentOverlay?.querySelector('.prompt-composer-textarea');
|
|
1348
|
+
if (currentTextarea) this._insertComposerText(currentTextarea, paths.join(' '));
|
|
1349
|
+
else if (app.sessions?.has(sessionId)) {
|
|
1350
|
+
const draft = this._composerDrafts.get(sessionId) || '';
|
|
1351
|
+
this._composerDrafts.set(sessionId, `${draft}${draft && !/\s$/.test(draft) ? ' ' : ''}${paths.join(' ')}`);
|
|
1352
|
+
this._syncComposerDraftIndicator();
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
} finally {
|
|
1356
|
+
const remaining = Math.max(0, (this._composerUploads.get(sessionId) || 1) - 1);
|
|
1357
|
+
if (remaining > 0) this._composerUploads.set(sessionId, remaining);
|
|
1358
|
+
else this._composerUploads.delete(sessionId);
|
|
1359
|
+
syncUploadUi();
|
|
1360
|
+
if (overlay.isConnected) fileInput.value = '';
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
textarea.addEventListener('input', saveDraft);
|
|
1365
|
+
textarea.addEventListener('paste', (event) => {
|
|
1366
|
+
const items = event.clipboardData?.items;
|
|
1367
|
+
if (!items) return;
|
|
1368
|
+
const images = Array.from(items)
|
|
1369
|
+
.filter((item) => item.type.startsWith('image/'))
|
|
1370
|
+
.map((item) => item.getAsFile())
|
|
1371
|
+
.filter(Boolean);
|
|
1372
|
+
if (images.length > 0) {
|
|
1373
|
+
event.preventDefault();
|
|
1374
|
+
void handleImages(images);
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
overlay.addEventListener('keydown', (event) => {
|
|
1378
|
+
if (event.key === 'Escape') {
|
|
1379
|
+
event.preventDefault();
|
|
1380
|
+
close();
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
imageButton.addEventListener('click', () => fileInput.click());
|
|
1384
|
+
fileInput.addEventListener('change', () => void handleImages(fileInput.files));
|
|
1385
|
+
overlay.querySelector('.prompt-composer-terminal').addEventListener('click', () => close({ focusTerminal: true }));
|
|
1386
|
+
overlay.querySelector('.paste-cancel').addEventListener('click', () => close());
|
|
1387
|
+
sendButton.addEventListener('click', send);
|
|
1388
|
+
overlay.addEventListener('click', (event) => {
|
|
1389
|
+
if (event.target === overlay) close();
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
document.body.appendChild(overlay);
|
|
1393
|
+
focusTrap.activate();
|
|
1394
|
+
textarea.focus();
|
|
1395
|
+
textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
|
|
1396
|
+
},
|
|
1397
|
+
|
|
1133
1398
|
/** Show a paste overlay for iOS compatibility.
|
|
1134
1399
|
* Handles three input paths from one dialog:
|
|
1135
1400
|
* - Text: long-press the textarea → Paste → Send (unchanged).
|
|
@@ -1259,9 +1524,9 @@ class FocusTrap {
|
|
|
1259
1524
|
});
|
|
1260
1525
|
}
|
|
1261
1526
|
|
|
1262
|
-
deactivate() {
|
|
1527
|
+
deactivate({ restoreFocus = true } = {}) {
|
|
1263
1528
|
this.element.removeEventListener('keydown', this.boundHandleKeydown);
|
|
1264
|
-
if (this.previouslyFocused && typeof this.previouslyFocused.focus === 'function') {
|
|
1529
|
+
if (restoreFocus && this.previouslyFocused && typeof this.previouslyFocused.focus === 'function') {
|
|
1265
1530
|
this.previouslyFocused.focus();
|
|
1266
1531
|
}
|
|
1267
1532
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|