@agi-fans/dsh-tui 0.24.0 → 0.24.1
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/lib/CHANGELOG.md +18 -2
- package/lib/chrome/theme.js +1 -1
- package/lib/chrome/theme.js.map +1 -1
- package/lib/chrome/tool-renderers.d.ts +2 -0
- package/lib/chrome/tool-renderers.js +5 -0
- package/lib/chrome/tool-renderers.js.map +1 -1
- package/lib/input/editor.d.ts +14 -0
- package/lib/input/editor.js +55 -5
- package/lib/input/editor.js.map +1 -1
- package/lib/input/image-paste.d.ts +20 -3
- package/lib/input/image-paste.js +67 -14
- package/lib/input/image-paste.js.map +1 -1
- package/lib/locale/zh.d.ts +1 -0
- package/lib/locale/zh.js +1 -0
- package/lib/locale/zh.js.map +1 -1
- package/lib/runtime/composer-images.d.ts +11 -11
- package/lib/runtime/composer-images.js +105 -96
- package/lib/runtime/composer-images.js.map +1 -1
- package/lib/runtime/provider-local.js +88 -58
- package/lib/runtime/provider-local.js.map +1 -1
- package/lib/runtime/runner.js +3 -3
- package/lib/runtime/runner.js.map +1 -1
- package/lib/session/composer-drafts.js +25 -1
- package/lib/session/composer-drafts.js.map +1 -1
- package/lib/session/session-controller.js +2 -3
- package/lib/session/session-controller.js.map +1 -1
- package/lib/views/transcript-render.js +10 -8
- package/lib/views/transcript-render.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,22 +7,25 @@
|
|
|
7
7
|
* decoding, and the prompt/settings/search branches that decide whether a
|
|
8
8
|
* paste is an image at all stay in the provider.
|
|
9
9
|
*/
|
|
10
|
-
import { imageMarker, probeImageDimensions,
|
|
10
|
+
import { imageMarker, probeImageDimensions, submissionDraft } from "../input/image-paste.js";
|
|
11
11
|
export class ComposerImages {
|
|
12
12
|
#deps;
|
|
13
|
-
#images =
|
|
13
|
+
#images = new Map();
|
|
14
|
+
#next = 0;
|
|
14
15
|
#revision = 0;
|
|
15
16
|
#validate;
|
|
16
17
|
constructor(deps) {
|
|
17
18
|
this.#deps = deps;
|
|
18
19
|
}
|
|
19
20
|
get count() {
|
|
20
|
-
return this.#
|
|
21
|
+
return this.#active().length;
|
|
21
22
|
}
|
|
22
23
|
get revision() { return this.#revision; }
|
|
23
24
|
/** Fresh copies for a submission snapshot. */
|
|
24
|
-
copies() {
|
|
25
|
-
|
|
25
|
+
copies(snapshot) {
|
|
26
|
+
if (snapshot === undefined)
|
|
27
|
+
this.reconcile();
|
|
28
|
+
return this.#active(snapshot).map(marker => ({ ...this.#images.get(marker.id) }));
|
|
26
29
|
}
|
|
27
30
|
setValidator(validate) {
|
|
28
31
|
this.#validate = validate;
|
|
@@ -32,127 +35,133 @@ export class ComposerImages {
|
|
|
32
35
|
* becomes an error notice and skips the draft, instead of failing the whole
|
|
33
36
|
* submission after the user has typed a prompt around it.
|
|
34
37
|
*/
|
|
35
|
-
async admit(image) {
|
|
38
|
+
async admit(image, signal) {
|
|
39
|
+
if (signal?.aborted === true)
|
|
40
|
+
return false;
|
|
36
41
|
if (this.#validate === undefined)
|
|
37
42
|
return true;
|
|
38
43
|
try {
|
|
39
44
|
await this.#validate(image);
|
|
40
|
-
return
|
|
45
|
+
return !signal?.aborted;
|
|
41
46
|
}
|
|
42
47
|
catch (error) {
|
|
43
|
-
|
|
48
|
+
if (!signal?.aborted)
|
|
49
|
+
this.#deps.notice(error instanceof Error ? error.message : String(error));
|
|
44
50
|
return false;
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
|
-
|
|
53
|
+
#active(snapshot) {
|
|
54
|
+
return (snapshot === undefined ? this.#deps.editor.markers() : snapshot.markers ?? []).filter(marker => this.#images.has(marker.id));
|
|
55
|
+
}
|
|
56
|
+
/** Expanded-text positions of the owned image markers, including their separators. */
|
|
57
|
+
ranges() {
|
|
58
|
+
const editor = this.#deps.editor;
|
|
59
|
+
return this.#active().map((marker, index) => ({ kind: 'image', index,
|
|
60
|
+
start: editor.textRange(0, marker.start).length, end: editor.textRange(0, marker.end).length }));
|
|
61
|
+
}
|
|
62
|
+
/** Insert an image as one undoable owned marker. */
|
|
48
63
|
insert(input) {
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
? probeImageDimensions(input.data, input.mediaType)
|
|
52
|
-
: undefined;
|
|
53
|
-
const image = {
|
|
54
|
-
...input,
|
|
64
|
+
const size = input.width === undefined || input.height === undefined ? probeImageDimensions(input.data, input.mediaType) : undefined;
|
|
65
|
+
const image = { ...input,
|
|
55
66
|
...(input.width === undefined && size !== undefined ? { width: size.width } : {}),
|
|
56
|
-
...(input.height === undefined && size !== undefined ? { height: size.height } : {})
|
|
57
|
-
};
|
|
58
|
-
const marker = imageMarker(this.#images.length, image);
|
|
67
|
+
...(input.height === undefined && size !== undefined ? { height: size.height } : {}) };
|
|
59
68
|
const editor = this.#deps.editor;
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
const start = editor.cursor;
|
|
70
|
+
const before = start > 0 && !/\s/u.test(editor.text[start - 1] ?? '') ? ' ' : '';
|
|
71
|
+
const after = start >= editor.text.length || !/\s/u.test(editor.text[start] ?? '') ? ' ' : '';
|
|
72
|
+
const text = before + imageMarker(this.count, image) + after;
|
|
73
|
+
editor.splice(start, start, text);
|
|
74
|
+
const id = `image:${++this.#next}`;
|
|
75
|
+
editor.mark(id, start, start + text.length);
|
|
76
|
+
this.#images.set(id, image);
|
|
77
|
+
this.#revision++;
|
|
78
|
+
this.reconcile();
|
|
66
79
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Delete the image whose marker touches the cursor, renumbering the rest.
|
|
69
|
-
* Returns true when a draft was removed; the caller repaints.
|
|
70
|
-
*/
|
|
71
80
|
removeAtCursor(key) {
|
|
72
81
|
const editor = this.#deps.editor;
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
from -= 1;
|
|
91
|
-
const oldImages = this.#images;
|
|
92
|
-
let text = editor.text.slice(0, from) + editor.text.slice(to);
|
|
93
|
-
const nextImages = oldImages.filter((_, oldIndex) => oldIndex !== index);
|
|
94
|
-
let nextIndex = 0;
|
|
95
|
-
for (let oldIndex = 0; oldIndex < oldImages.length; oldIndex += 1) {
|
|
96
|
-
if (oldIndex === index)
|
|
97
|
-
continue;
|
|
98
|
-
const remaining = oldImages[oldIndex];
|
|
99
|
-
text = text.replaceAll(imageMarker(oldIndex, remaining), imageMarker(nextIndex, remaining));
|
|
100
|
-
nextIndex += 1;
|
|
82
|
+
const marker = this.#active().find(marker => key === 'backspace'
|
|
83
|
+
? editor.cursor > marker.start && editor.cursor <= marker.end
|
|
84
|
+
: editor.cursor >= marker.start && editor.cursor < marker.end);
|
|
85
|
+
if (marker === undefined)
|
|
86
|
+
return false;
|
|
87
|
+
editor.splice(marker.start, marker.end, '');
|
|
88
|
+
this.reconcile();
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/** Keep payloads referenced by current text or undo history, and relabel active images. */
|
|
92
|
+
reconcile() {
|
|
93
|
+
const editor = this.#deps.editor;
|
|
94
|
+
const retained = editor.retainedMarkerIds();
|
|
95
|
+
for (const id of this.#images.keys()) {
|
|
96
|
+
if (!retained.has(id)) {
|
|
97
|
+
this.#images.delete(id);
|
|
98
|
+
this.#revision++;
|
|
101
99
|
}
|
|
102
|
-
this.#images = nextImages;
|
|
103
|
-
this.#revision++;
|
|
104
|
-
editor.replaceText(text, Math.min(from, text.length));
|
|
105
|
-
return true;
|
|
106
100
|
}
|
|
107
|
-
|
|
101
|
+
for (const [index, { id }] of this.#active().entries()) {
|
|
102
|
+
const marker = editor.markers().find(marker => marker.id === id);
|
|
103
|
+
const current = editor.text.slice(marker.start, marker.end);
|
|
104
|
+
const text = (current.match(/^\s*/u)?.[0] ?? '') + imageMarker(index, this.#images.get(marker.id)) + (current.match(/\s*$/u)?.[0] ?? '');
|
|
105
|
+
if (text !== current)
|
|
106
|
+
editor.rewriteMarker(marker.id, text);
|
|
107
|
+
}
|
|
108
108
|
}
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return;
|
|
109
|
+
/** Bind persisted payloads to the editor's saved marker identities. */
|
|
110
|
+
restoreSnapshot(images) {
|
|
111
|
+
this.#images.clear();
|
|
113
112
|
const editor = this.#deps.editor;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
113
|
+
let markers = editor.markers().filter(marker => marker.id.startsWith('image:'));
|
|
114
|
+
if (markers.length === 0) {
|
|
115
|
+
// Drafts saved before marker ranges were recorded retain their old labels.
|
|
116
|
+
let cursor = 0;
|
|
117
|
+
for (const [index, image] of images.entries()) {
|
|
118
|
+
const label = imageMarker(index, image);
|
|
119
|
+
const start = editor.text.indexOf(label, cursor);
|
|
120
|
+
if (start < 0)
|
|
121
|
+
continue;
|
|
122
|
+
cursor = start + label.length;
|
|
123
|
+
editor.mark(`image:${++this.#next}`, start, cursor);
|
|
124
|
+
}
|
|
125
|
+
markers = editor.markers().filter(marker => marker.id.startsWith('image:'));
|
|
126
|
+
}
|
|
127
|
+
for (const [index, marker] of markers.entries()) {
|
|
128
|
+
this.#next = Math.max(this.#next, Number(marker.id.slice('image:'.length)) || 0);
|
|
129
|
+
if (images[index] !== undefined)
|
|
130
|
+
this.#images.set(marker.id, { ...images[index] });
|
|
127
131
|
}
|
|
128
|
-
this.#images = retained;
|
|
129
132
|
this.#revision++;
|
|
130
|
-
editor.replaceText(text, Math.min(editor.cursor, text.length));
|
|
131
133
|
}
|
|
132
|
-
/** Prepend
|
|
134
|
+
/** Prepend restored input with an exact edit so repeated paste labels keep their payloads. */
|
|
133
135
|
restore(submission, fileMarkers) {
|
|
134
|
-
this.#revision++;
|
|
135
136
|
const editor = this.#deps.editor;
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
const draft = submissionDraft(submission, fileMarkers);
|
|
138
|
+
const separator = draft.text !== '' && editor.text !== '' ? '\n' : '';
|
|
139
|
+
editor.splice(0, 0, draft.text + separator, draft.text.length + separator.length + editor.text.length);
|
|
140
|
+
const last = draft.ranges.at(-1);
|
|
141
|
+
if (submission.text === '' && last?.kind === 'image' && last.end === draft.text.length)
|
|
142
|
+
last.end += separator.length;
|
|
143
|
+
for (const range of draft.ranges) {
|
|
144
|
+
if (range.kind !== 'image')
|
|
145
|
+
continue;
|
|
146
|
+
const id = `image:${++this.#next}`;
|
|
147
|
+
editor.mark(id, range.start, range.end);
|
|
148
|
+
this.#images.set(id, { ...submission.images[range.index] });
|
|
141
149
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
this.#images = [...submission.images.map(image => ({ ...image })), ...currentImages];
|
|
145
|
-
editor.replaceText(restored + separator + rebasedCurrent);
|
|
150
|
+
this.#revision++;
|
|
151
|
+
this.reconcile();
|
|
146
152
|
}
|
|
147
|
-
/** Replace drafts and text with a queued/edited submission. */
|
|
148
153
|
replace(submission, fileMarkers) {
|
|
149
|
-
|
|
150
|
-
this.#
|
|
151
|
-
|
|
154
|
+
const draft = submissionDraft(submission, fileMarkers);
|
|
155
|
+
this.#deps.editor.setText(draft.text);
|
|
156
|
+
for (const range of draft.ranges) {
|
|
157
|
+
if (range.kind === 'image')
|
|
158
|
+
this.#deps.editor.mark(`image:${++this.#next}`, range.start, range.end);
|
|
159
|
+
}
|
|
160
|
+
this.restoreSnapshot(submission.images);
|
|
152
161
|
}
|
|
153
162
|
clear() {
|
|
154
163
|
this.#revision++;
|
|
155
|
-
this.#images
|
|
164
|
+
this.#images.clear();
|
|
156
165
|
}
|
|
157
166
|
}
|
|
158
167
|
//# sourceMappingURL=composer-images.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer-images.js","sourceRoot":"","sources":["../../src/runtime/composer-images.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"composer-images.js","sourceRoot":"","sources":["../../src/runtime/composer-images.ts"],"names":[],"mappings":"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"}
|
|
@@ -87,7 +87,7 @@ import { edgeIndex, listNavigation, navigateIndex } from "../views/list-navigati
|
|
|
87
87
|
import { refreshProjectContext, resolveGitStatusFiles, resolveProjectContext } from "../session/project-context.js";
|
|
88
88
|
import { modelRouteReady } from "../session/model-readiness.js";
|
|
89
89
|
import { formatEssentialHotkeysText, formatHotkeysText, keysForAction } from "../views/hotkeys.js";
|
|
90
|
-
import { imagePathCandidates, splitComposerAttachmentMarkers,
|
|
90
|
+
import { imagePathCandidates, splitComposerAttachmentMarkers, readImageFile, readImageFromClipboard, readMacClipboardFiles, } from "../input/image-paste.js";
|
|
91
91
|
import { APP_DISPLAY_NAME, APP_NAME, APP_VERSION } from "../session/package-metadata.js";
|
|
92
92
|
const DOUBLE_CTRL_C_MS = 500;
|
|
93
93
|
/**
|
|
@@ -316,6 +316,7 @@ export class LocalTui {
|
|
|
316
316
|
#paste = false;
|
|
317
317
|
#pasteBuf = '';
|
|
318
318
|
#pasteInFlight = 0;
|
|
319
|
+
#pasteAbort;
|
|
319
320
|
#deferredPasteEvents = [];
|
|
320
321
|
#images = new ComposerImages({
|
|
321
322
|
editor: this.#editor,
|
|
@@ -889,6 +890,7 @@ export class LocalTui {
|
|
|
889
890
|
this.#render();
|
|
890
891
|
}
|
|
891
892
|
async reviewFiles(request) {
|
|
893
|
+
this.#cancelPaste();
|
|
892
894
|
if (!this.#tty || this.#disposed || request.signal.aborted)
|
|
893
895
|
return;
|
|
894
896
|
this.#workspacePress = undefined;
|
|
@@ -1244,6 +1246,7 @@ export class LocalTui {
|
|
|
1244
1246
|
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
1245
1247
|
}
|
|
1246
1248
|
}
|
|
1249
|
+
this.#cancelPaste();
|
|
1247
1250
|
this.#flushPasteBurst(true);
|
|
1248
1251
|
this.#pasteBurst.clear();
|
|
1249
1252
|
if (request.notify !== false) {
|
|
@@ -1607,8 +1610,8 @@ export class LocalTui {
|
|
|
1607
1610
|
this.#reportDraftError('restore', error);
|
|
1608
1611
|
}
|
|
1609
1612
|
if (existing !== undefined) {
|
|
1610
|
-
images.replace({ text: existing.editor.text, images: existing.images });
|
|
1611
1613
|
editor.restore(existing.editor);
|
|
1614
|
+
images.restoreSnapshot(existing.images);
|
|
1612
1615
|
files.restoreSnapshot(existing.files);
|
|
1613
1616
|
}
|
|
1614
1617
|
images.restore(submission, files.adopt(submission.files ?? []));
|
|
@@ -1842,6 +1845,7 @@ export class LocalTui {
|
|
|
1842
1845
|
this.#flushDraft();
|
|
1843
1846
|
this.#pendingInputs.flush(this.#pendingScope());
|
|
1844
1847
|
this.#disposed = true;
|
|
1848
|
+
this.#cancelPaste();
|
|
1845
1849
|
this.#imagePreviewAbort?.abort();
|
|
1846
1850
|
this.#fileReviewAbort?.abort();
|
|
1847
1851
|
this.#closeFileBrowser();
|
|
@@ -2049,11 +2053,11 @@ export class LocalTui {
|
|
|
2049
2053
|
if (this.#historyIndex > 0 && this.#historyDraft !== undefined)
|
|
2050
2054
|
return this.#historyDraft;
|
|
2051
2055
|
const editor = this.#prompt?.savedInput ?? this.#editor.snapshot();
|
|
2052
|
-
return { editor, images: this.#images.copies(), files: this.#files.snapshot(editor.text), literal: this.#literalInput };
|
|
2056
|
+
return { editor, images: this.#images.copies(editor), files: this.#files.snapshot(editor.text), literal: this.#literalInput };
|
|
2053
2057
|
}
|
|
2054
2058
|
#restoreDraft(draft) {
|
|
2055
|
-
this.#images.replace({ text: draft.editor.text, images: draft.images });
|
|
2056
2059
|
this.#editor.restore(draft.editor);
|
|
2060
|
+
this.#images.restoreSnapshot(draft.images);
|
|
2057
2061
|
this.#files.restoreSnapshot(draft.files);
|
|
2058
2062
|
this.#literalInput = draft.literal;
|
|
2059
2063
|
}
|
|
@@ -2117,6 +2121,7 @@ export class LocalTui {
|
|
|
2117
2121
|
#switchDraft(id) {
|
|
2118
2122
|
if (id === this.#draftScope)
|
|
2119
2123
|
return;
|
|
2124
|
+
this.#cancelPaste();
|
|
2120
2125
|
this.#goalAbort?.abort();
|
|
2121
2126
|
this.#goalAbort = undefined;
|
|
2122
2127
|
this.#queueHotkeyAbort?.abort();
|
|
@@ -2509,14 +2514,26 @@ export class LocalTui {
|
|
|
2509
2514
|
for (const event of this.#pasteBurst.flush(Date.now(), force))
|
|
2510
2515
|
this.#deliverPasteEmission(event);
|
|
2511
2516
|
}
|
|
2517
|
+
#cancelPaste() {
|
|
2518
|
+
this.#pasteAbort?.abort();
|
|
2519
|
+
this.#pasteAbort = undefined;
|
|
2520
|
+
this.#pasteInFlight = 0;
|
|
2521
|
+
this.#deferredPasteEvents = [];
|
|
2522
|
+
this.#paste = false;
|
|
2523
|
+
this.#pasteBuf = '';
|
|
2524
|
+
}
|
|
2512
2525
|
#startAsyncPaste(operation) {
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2526
|
+
const abort = new AbortController();
|
|
2527
|
+
this.#pasteAbort = abort;
|
|
2528
|
+
this.#pasteInFlight = 1;
|
|
2529
|
+
void operation(abort.signal).catch((error) => {
|
|
2530
|
+
if (!abort.signal.aborted)
|
|
2531
|
+
this.notice('Paste failed: ' + (error instanceof Error ? error.message : String(error)), { level: 'error' });
|
|
2516
2532
|
}).finally(() => {
|
|
2517
|
-
|
|
2518
|
-
if (this.#pasteInFlight !== 0 || this.#deferredPasteEvents.length === 0)
|
|
2533
|
+
if (this.#pasteAbort !== abort)
|
|
2519
2534
|
return;
|
|
2535
|
+
this.#pasteAbort = undefined;
|
|
2536
|
+
this.#pasteInFlight = 0;
|
|
2520
2537
|
const deferred = this.#deferredPasteEvents.splice(0);
|
|
2521
2538
|
for (let index = 0; index < deferred.length; index += 1) {
|
|
2522
2539
|
this.#dispatch(deferred[index]);
|
|
@@ -2527,7 +2544,10 @@ export class LocalTui {
|
|
|
2527
2544
|
}
|
|
2528
2545
|
});
|
|
2529
2546
|
}
|
|
2530
|
-
async #acceptPastedText(text) {
|
|
2547
|
+
async #acceptPastedText(text, signal) {
|
|
2548
|
+
if (signal.aborted)
|
|
2549
|
+
return;
|
|
2550
|
+
text = text.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
|
2531
2551
|
if (this.#fileBrowser !== null) {
|
|
2532
2552
|
this.#applyFileBrowserInput({ type: 'text', value: text });
|
|
2533
2553
|
return;
|
|
@@ -2555,42 +2575,50 @@ export class LocalTui {
|
|
|
2555
2575
|
}
|
|
2556
2576
|
const paths = imagePathCandidates(text);
|
|
2557
2577
|
if (paths.length > 0) {
|
|
2558
|
-
const images = await Promise.all(paths.map(path =>
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
for (const image of images) {
|
|
2562
|
-
if (await this.#images.admit(image)) {
|
|
2563
|
-
this.#images.insert(image);
|
|
2564
|
-
inserted = true;
|
|
2565
|
-
}
|
|
2578
|
+
const images = await Promise.all(paths.map(async (path) => {
|
|
2579
|
+
try {
|
|
2580
|
+
return await this.#readImagePath(path);
|
|
2566
2581
|
}
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
this.#render();
|
|
2582
|
+
catch {
|
|
2583
|
+
return null;
|
|
2570
2584
|
}
|
|
2585
|
+
}));
|
|
2586
|
+
if (signal.aborted)
|
|
2571
2587
|
return;
|
|
2588
|
+
let inserted = false;
|
|
2589
|
+
for (const [index, image] of images.entries()) {
|
|
2590
|
+
if (image === null) {
|
|
2591
|
+
this.notice(uiText(resolveUiLanguage(this.#uiLanguage), 'Could not read image {path}', { path: paths[index] }), { level: 'error' });
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
const admitted = await this.#images.admit(image, signal);
|
|
2595
|
+
if (signal.aborted)
|
|
2596
|
+
return;
|
|
2597
|
+
if (admitted) {
|
|
2598
|
+
this.#images.insert(image);
|
|
2599
|
+
inserted = true;
|
|
2600
|
+
}
|
|
2572
2601
|
}
|
|
2573
|
-
|
|
2574
|
-
// it before Node reads it. OMP falls back to the still-live clipboard
|
|
2575
|
-
// image instead of leaking that stale path into the prompt.
|
|
2576
|
-
const clipboardImage = await this.#readClipboardImage();
|
|
2577
|
-
if (clipboardImage !== null) {
|
|
2578
|
-
this.#images.insert(clipboardImage);
|
|
2602
|
+
if (inserted)
|
|
2579
2603
|
this.#refreshAutocomplete();
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
}
|
|
2604
|
+
this.#render();
|
|
2605
|
+
return;
|
|
2583
2606
|
}
|
|
2584
2607
|
this.#literalInput ||= this.#editor.cursor === 0 && text.startsWith('!');
|
|
2585
2608
|
this.#editor.paste(text);
|
|
2586
2609
|
this.#refreshAutocomplete();
|
|
2587
2610
|
this.#render();
|
|
2588
2611
|
}
|
|
2589
|
-
async #pasteClipboard() {
|
|
2590
|
-
if (this.#search === null && this.#settings === null) {
|
|
2612
|
+
async #pasteClipboard(signal) {
|
|
2613
|
+
if (this.#prompt === null && this.#search === null && this.#settings === null) {
|
|
2591
2614
|
const image = await this.#readClipboardImage();
|
|
2615
|
+
if (signal.aborted)
|
|
2616
|
+
return;
|
|
2592
2617
|
if (image !== null) {
|
|
2593
|
-
|
|
2618
|
+
const admitted = await this.#images.admit(image, signal);
|
|
2619
|
+
if (signal.aborted)
|
|
2620
|
+
return;
|
|
2621
|
+
if (admitted) {
|
|
2594
2622
|
this.#images.insert(image);
|
|
2595
2623
|
this.#refreshAutocomplete();
|
|
2596
2624
|
this.#render();
|
|
@@ -2598,29 +2626,17 @@ export class LocalTui {
|
|
|
2598
2626
|
return;
|
|
2599
2627
|
}
|
|
2600
2628
|
const files = await this.#readClipboardFiles();
|
|
2629
|
+
if (signal.aborted)
|
|
2630
|
+
return;
|
|
2601
2631
|
const imagePaths = files.filter(path => imagePathCandidates(path).length === 1);
|
|
2602
2632
|
if (imagePaths.length > 0) {
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
if (images.length > 0) {
|
|
2606
|
-
let inserted = false;
|
|
2607
|
-
for (const candidate of images) {
|
|
2608
|
-
if (await this.#images.admit(candidate)) {
|
|
2609
|
-
this.#images.insert(candidate);
|
|
2610
|
-
inserted = true;
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
|
-
if (inserted) {
|
|
2614
|
-
this.#refreshAutocomplete();
|
|
2615
|
-
this.#render();
|
|
2616
|
-
}
|
|
2617
|
-
return;
|
|
2618
|
-
}
|
|
2633
|
+
await this.#acceptPastedText(imagePaths.join('\n'), signal);
|
|
2634
|
+
return;
|
|
2619
2635
|
}
|
|
2620
2636
|
}
|
|
2621
2637
|
const text = await this.#readClipboard();
|
|
2622
|
-
if (text !== '')
|
|
2623
|
-
await this.#acceptPastedText(text);
|
|
2638
|
+
if (text !== '' && !signal.aborted)
|
|
2639
|
+
await this.#acceptPastedText(text, signal);
|
|
2624
2640
|
}
|
|
2625
2641
|
/** Resolve one bounded tmux probe without overwriting an explicit preference. */
|
|
2626
2642
|
setTmuxMouseEnabled(enabled) {
|
|
@@ -3208,6 +3224,8 @@ export class LocalTui {
|
|
|
3208
3224
|
// Clipboard image inspection is asynchronous. Preserve the exact key
|
|
3209
3225
|
// order so a fast Ctrl+V, Enter submits the finished image draft rather
|
|
3210
3226
|
// than an empty prompt.
|
|
3227
|
+
if (this.#pasteInFlight > 0 && event.type === 'key' && (event.id === 'ctrl+c' || event.id === 'escape'))
|
|
3228
|
+
this.#cancelPaste();
|
|
3211
3229
|
if (this.#pasteInFlight > 0) {
|
|
3212
3230
|
this.#deferredPasteEvents.push(event);
|
|
3213
3231
|
return;
|
|
@@ -3218,7 +3236,7 @@ export class LocalTui {
|
|
|
3218
3236
|
const text = this.#pasteBuf.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
|
3219
3237
|
this.#pasteBuf = '';
|
|
3220
3238
|
if (text !== '')
|
|
3221
|
-
this.#startAsyncPaste(this.#acceptPastedText(text));
|
|
3239
|
+
this.#startAsyncPaste(signal => this.#acceptPastedText(text, signal));
|
|
3222
3240
|
return;
|
|
3223
3241
|
}
|
|
3224
3242
|
if (event.type === 'text')
|
|
@@ -4606,11 +4624,20 @@ export class LocalTui {
|
|
|
4606
4624
|
#buildSubmission(text) {
|
|
4607
4625
|
const images = this.#images.copies();
|
|
4608
4626
|
const files = this.#files.copies();
|
|
4609
|
-
const
|
|
4627
|
+
const markers = this.#files.snapshot(this.#editor.text).entries.map(item => item.marker);
|
|
4628
|
+
const ranges = this.#images.ranges();
|
|
4629
|
+
for (const [index, marker] of markers.entries()) {
|
|
4630
|
+
const start = text.indexOf(marker);
|
|
4631
|
+
if (start < 0)
|
|
4632
|
+
continue;
|
|
4633
|
+
const end = start + marker.length;
|
|
4634
|
+
ranges.push({ kind: 'file', index, start: start > 0 && text[start - 1] === ' ' ? start - 1 : start,
|
|
4635
|
+
end: text[end] === ' ' ? end + 1 : end });
|
|
4636
|
+
}
|
|
4610
4637
|
const segments = images.length === 0 && files.length === 0 ? []
|
|
4611
|
-
: splitComposerAttachmentMarkers(text
|
|
4612
|
-
|
|
4613
|
-
|
|
4638
|
+
: splitComposerAttachmentMarkers(text, images, markers, images.length > 0 && text === this.#editor.expandedText ? ranges : undefined);
|
|
4639
|
+
const submittedText = images.length === 0 ? this.#files.text(text)
|
|
4640
|
+
: segments.flatMap(segment => segment.kind === 'text' ? [segment.text] : []).join('');
|
|
4614
4641
|
const literal = text === this.#editor.expandedText && (this.#literalInput || this.#editor.startsWithPaste);
|
|
4615
4642
|
const historyText = submittedText.replace(/[ \t]{2,}/gu, ' ').trim();
|
|
4616
4643
|
return {
|
|
@@ -4829,6 +4856,7 @@ export class LocalTui {
|
|
|
4829
4856
|
const pending = this.#prompt;
|
|
4830
4857
|
if (pending === null)
|
|
4831
4858
|
return;
|
|
4859
|
+
this.#cancelPaste();
|
|
4832
4860
|
const draft = structuredPromptAnswer(pending, this.#editor.expandedText);
|
|
4833
4861
|
try {
|
|
4834
4862
|
pending.request.onAnswerDraft?.(draft);
|
|
@@ -4979,6 +5007,7 @@ export class LocalTui {
|
|
|
4979
5007
|
}
|
|
4980
5008
|
this.#search = null;
|
|
4981
5009
|
this.#ac = null;
|
|
5010
|
+
this.#cancelPaste();
|
|
4982
5011
|
this.#settings = createSettings(this.prefs(), undefined, this.#agentBehavior, this.#features, this.#pluginSettings?.entries(), discoverEditors(this.#externalEditor), this.#settingsActions());
|
|
4983
5012
|
this.#refreshSettingsCatalog();
|
|
4984
5013
|
this.#render();
|
|
@@ -5391,7 +5420,7 @@ export class LocalTui {
|
|
|
5391
5420
|
return true;
|
|
5392
5421
|
}
|
|
5393
5422
|
if (action === 'paste-clipboard') {
|
|
5394
|
-
this.#startAsyncPaste(this.#pasteClipboard());
|
|
5423
|
+
this.#startAsyncPaste(signal => this.#pasteClipboard(signal));
|
|
5395
5424
|
return true;
|
|
5396
5425
|
}
|
|
5397
5426
|
// Explicit queue delivery is independent of the busy-Enter preference.
|
|
@@ -5407,7 +5436,8 @@ export class LocalTui {
|
|
|
5407
5436
|
this.#handTerminal(() => {
|
|
5408
5437
|
this.#literalInput ||= this.#editor.startsWithPaste;
|
|
5409
5438
|
const text = editExternally(this.#editor.expandedText, this.#externalEditor);
|
|
5410
|
-
this.#editor.
|
|
5439
|
+
while (this.#editor.expandPaste()) { /* Keep marker positions aligned with expanded source. */ }
|
|
5440
|
+
this.#editor.replaceText(text);
|
|
5411
5441
|
this.#images.reconcile();
|
|
5412
5442
|
});
|
|
5413
5443
|
}
|