skydive-cli 0.1.0-beta.276 → 0.1.0-beta.286
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/dist/js/bin.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { createParser } from "eventsource-parser";
|
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
|
|
18
18
|
//#region package.json
|
|
19
|
-
var version$1 = "0.1.0-beta.
|
|
19
|
+
var version$1 = "0.1.0-beta.286";
|
|
20
20
|
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region src/types.ts
|
|
@@ -2137,7 +2137,7 @@ const chatCommand = {
|
|
|
2137
2137
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
2138
2138
|
process.exit(1);
|
|
2139
2139
|
}
|
|
2140
|
-
const { runChat } = await import("./boot-
|
|
2140
|
+
const { runChat } = await import("./boot-D23rGRlW.mjs");
|
|
2141
2141
|
await runChat({
|
|
2142
2142
|
appUrl,
|
|
2143
2143
|
sessionToken: session.value.sessionToken,
|
|
@@ -3345,7 +3345,7 @@ const switchCommand = {
|
|
|
3345
3345
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
3346
3346
|
process.exit(1);
|
|
3347
3347
|
}
|
|
3348
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
3348
|
+
const { runWorkspacePicker } = await import("./boot-D23rGRlW.mjs");
|
|
3349
3349
|
await runWorkspacePicker(session);
|
|
3350
3350
|
return;
|
|
3351
3351
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { A as themesForMode, C as monoTheme, D as themeMode, E as themeForMode, F as DEFAULT_APP_URL, I as getConfigPath, L as getSavedTheme, M as listWorkspaces, N as setActiveWorkspace, O as themeModeFromColorFgBg, P as DEFAULT_API_URL, R as resolveWebUrl, S as findTheme, T as theme, _ as isRecord, b as DEFAULT_THEME_ID, c as resolveAgent, d as parseExternalOauthConnectParams, f as parseOauthConnectParams, g as errorMessage, h as parseConnectCard, j as getActiveWorkspaceId, k as themeVersion, l as MASK_CHAR, m as resolveConnectUrl, p as reconcileMaskedInput, u as cardActionErrorMessage, v as HttpError, w as noColorRequested, x as applyTheme, y as createRestClient, z as saveTheme } from "./bin.mjs";
|
|
3
3
|
import { t as PortalClient } from "./client-_OL8-XGH.mjs";
|
|
4
|
-
import path, { basename, isAbsolute, join, win32 } from "node:path";
|
|
4
|
+
import path, { basename, extname, isAbsolute, join, win32 } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import open from "open";
|
|
7
7
|
import { execFile, spawn } from "node:child_process";
|
|
@@ -1559,7 +1559,7 @@ function parseDroppedPaths(text) {
|
|
|
1559
1559
|
i += 1;
|
|
1560
1560
|
continue;
|
|
1561
1561
|
}
|
|
1562
|
-
if (
|
|
1562
|
+
if (ch === " " || ch === " ") {
|
|
1563
1563
|
if (current) {
|
|
1564
1564
|
tokens.push(current);
|
|
1565
1565
|
current = "";
|
|
@@ -1585,6 +1585,28 @@ function parseDroppedPaths(text) {
|
|
|
1585
1585
|
}
|
|
1586
1586
|
return paths;
|
|
1587
1587
|
}
|
|
1588
|
+
const TEXT_MIME_BY_EXT = {
|
|
1589
|
+
".txt": "text/plain",
|
|
1590
|
+
".md": "text/markdown",
|
|
1591
|
+
".markdown": "text/markdown",
|
|
1592
|
+
".csv": "text/csv",
|
|
1593
|
+
".tsv": "text/tab-separated-values",
|
|
1594
|
+
".json": "application/json",
|
|
1595
|
+
".yaml": "application/yaml",
|
|
1596
|
+
".yml": "application/yaml",
|
|
1597
|
+
".xml": "application/xml",
|
|
1598
|
+
".html": "text/html",
|
|
1599
|
+
".htm": "text/html",
|
|
1600
|
+
".css": "text/css",
|
|
1601
|
+
".js": "text/javascript",
|
|
1602
|
+
".ts": "text/plain",
|
|
1603
|
+
".tsx": "text/plain",
|
|
1604
|
+
".jsx": "text/plain",
|
|
1605
|
+
".py": "text/x-python",
|
|
1606
|
+
".sh": "text/x-shellscript",
|
|
1607
|
+
".log": "text/plain",
|
|
1608
|
+
".svg": "image/svg+xml"
|
|
1609
|
+
};
|
|
1588
1610
|
/** Decide how to handle a paste. `kind: 'binary'` events carry the mime of
|
|
1589
1611
|
* bytes the terminal forwarded; text events carry the decoded paste text.
|
|
1590
1612
|
*
|
|
@@ -1610,25 +1632,30 @@ function routePaste(input) {
|
|
|
1610
1632
|
return { kind: "text" };
|
|
1611
1633
|
}
|
|
1612
1634
|
/**
|
|
1613
|
-
* Resolve path candidates into
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1635
|
+
* Resolve path candidates into attachable files. Every path must be an
|
|
1636
|
+
* existing regular file; otherwise the caller should restore the original
|
|
1637
|
+
* paste as text (an all-or-nothing rule so a paste meant as text — which
|
|
1638
|
+
* merely *looks* path-shaped — is never partially eaten).
|
|
1639
|
+
*
|
|
1640
|
+
* Any file type attaches. The mediaType comes from magic bytes when
|
|
1641
|
+
* detectable, an extension map for the plain-text formats magic can't see,
|
|
1642
|
+
* and application/octet-stream as the last resort — the attachments API
|
|
1643
|
+
* accepts any mediaType (100MB cap enforced server-side at presign).
|
|
1616
1644
|
*/
|
|
1617
|
-
async function
|
|
1645
|
+
async function resolveDroppedFiles(paths) {
|
|
1618
1646
|
try {
|
|
1619
|
-
const
|
|
1647
|
+
const files = await Promise.all(paths.map(async (path) => {
|
|
1620
1648
|
if (!(await stat(path)).isFile()) return null;
|
|
1621
1649
|
const data = new Uint8Array(await readFile(path));
|
|
1622
|
-
const
|
|
1623
|
-
if (!detected?.mime.startsWith("image/")) return null;
|
|
1650
|
+
const mediaType = (await fileTypeFromBuffer(data))?.mime ?? TEXT_MIME_BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
1624
1651
|
return {
|
|
1625
1652
|
fileName: basename(path),
|
|
1626
|
-
mediaType
|
|
1653
|
+
mediaType,
|
|
1627
1654
|
data
|
|
1628
1655
|
};
|
|
1629
1656
|
}));
|
|
1630
|
-
if (!
|
|
1631
|
-
return
|
|
1657
|
+
if (!files.every((file) => file !== null)) return null;
|
|
1658
|
+
return files;
|
|
1632
1659
|
} catch (_error) {
|
|
1633
1660
|
return null;
|
|
1634
1661
|
}
|
|
@@ -2774,12 +2801,15 @@ function Row({ barColor, children }) {
|
|
|
2774
2801
|
}
|
|
2775
2802
|
function RenderItem({ item }) {
|
|
2776
2803
|
switch (item.kind) {
|
|
2777
|
-
case "user": return /* @__PURE__ */
|
|
2804
|
+
case "user": return /* @__PURE__ */ jsxs(Row, {
|
|
2778
2805
|
barColor: theme.accent,
|
|
2779
|
-
children: /* @__PURE__ */ jsx("text", {
|
|
2806
|
+
children: [item.text ? /* @__PURE__ */ jsx("text", {
|
|
2780
2807
|
fg: theme.user,
|
|
2781
2808
|
children: item.text
|
|
2782
|
-
})
|
|
2809
|
+
}) : null, item.attachments && item.attachments.length > 0 ? /* @__PURE__ */ jsxs("text", {
|
|
2810
|
+
fg: theme.muted,
|
|
2811
|
+
children: ["⎘ ", item.attachments.join(" · ")]
|
|
2812
|
+
}) : null]
|
|
2783
2813
|
});
|
|
2784
2814
|
case "pending-steer": return /* @__PURE__ */ jsx(Row, {
|
|
2785
2815
|
barColor: theme.dim,
|
|
@@ -3200,10 +3230,15 @@ function uiMessagesToItems(messages) {
|
|
|
3200
3230
|
for (const m of messages) {
|
|
3201
3231
|
if (m.role === "user") {
|
|
3202
3232
|
const text = m.parts.map((p) => p.type === "text" ? p.text : "").join("").trim();
|
|
3203
|
-
|
|
3233
|
+
const attachments = m.parts.filter((p) => p.type === "data-anyone-attachment").map((p) => {
|
|
3234
|
+
const data = isRecord(p) && "data" in p && isRecord(p.data) ? p.data : null;
|
|
3235
|
+
return data && typeof data.fileName === "string" ? data.fileName : "file";
|
|
3236
|
+
});
|
|
3237
|
+
if (text || attachments.length > 0) out.push({
|
|
3204
3238
|
kind: "user",
|
|
3205
3239
|
id: m.id,
|
|
3206
|
-
text
|
|
3240
|
+
text,
|
|
3241
|
+
...attachments.length > 0 ? { attachments } : {}
|
|
3207
3242
|
});
|
|
3208
3243
|
continue;
|
|
3209
3244
|
}
|
|
@@ -4939,6 +4974,7 @@ const composerKeyBindings = [
|
|
|
4939
4974
|
];
|
|
4940
4975
|
/** Cap composer growth; past this the textarea scrolls its content instead. */
|
|
4941
4976
|
const maxComposerRows = 8;
|
|
4977
|
+
const maxAttachments = 10;
|
|
4942
4978
|
function isNewConversation(c) {
|
|
4943
4979
|
return "kind" in c && c.kind === "new";
|
|
4944
4980
|
}
|
|
@@ -4947,12 +4983,13 @@ function formatBytes(bytes) {
|
|
|
4947
4983
|
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
4948
4984
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
4949
4985
|
}
|
|
4950
|
-
/**
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4986
|
+
/** Status glyph for a staged attachment chip: uploading / ready / failed. */
|
|
4987
|
+
function attachmentGlyph(a) {
|
|
4988
|
+
switch (a.status) {
|
|
4989
|
+
case "uploading": return "↑";
|
|
4990
|
+
case "ready": return "✓";
|
|
4991
|
+
case "error": return `✗ ${a.errorText ?? "upload failed"}`;
|
|
4992
|
+
}
|
|
4956
4993
|
}
|
|
4957
4994
|
/**
|
|
4958
4995
|
* Open a URL in the user's local browser. Best-effort: over SSH there may be
|
|
@@ -4994,14 +5031,16 @@ function ChatScreen({ agent, conversation }) {
|
|
|
4994
5031
|
const [helpOpen, setHelpOpen] = useState(false);
|
|
4995
5032
|
const [ctrlCArmed, setCtrlCArmed] = useState(false);
|
|
4996
5033
|
const [composerRows, setComposerRows] = useState(1);
|
|
4997
|
-
const [
|
|
4998
|
-
const
|
|
5034
|
+
const [attachments, setAttachments] = useState([]);
|
|
5035
|
+
const uploadsRef = useRef(/* @__PURE__ */ new Map());
|
|
4999
5036
|
const [credPrompt, setCredPrompt] = useState(null);
|
|
5000
5037
|
const credPromptOpen = credPrompt !== null;
|
|
5001
5038
|
const runRef = useRef(run);
|
|
5002
5039
|
runRef.current = run;
|
|
5003
5040
|
const itemsRef = useRef(items);
|
|
5004
5041
|
itemsRef.current = items;
|
|
5042
|
+
const attachmentsRef = useRef(attachments);
|
|
5043
|
+
attachmentsRef.current = attachments;
|
|
5005
5044
|
const inputRef = useRef(input);
|
|
5006
5045
|
inputRef.current = input;
|
|
5007
5046
|
const credPromptRef = useRef(credPrompt);
|
|
@@ -5021,8 +5060,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5021
5060
|
const scrollRef = useRef(null);
|
|
5022
5061
|
const composerRef = useRef(null);
|
|
5023
5062
|
const composerBoxHeight = composerRows + 2;
|
|
5024
|
-
const pendingVisible = !grantPrompt && !credPrompt &&
|
|
5025
|
-
const pendingRows = pendingVisible ?
|
|
5063
|
+
const pendingVisible = !grantPrompt && !credPrompt && attachments.length > 0;
|
|
5064
|
+
const pendingRows = pendingVisible ? attachments.length + 2 : 0;
|
|
5026
5065
|
const credPromptHeight = 4 + (credPrompt && (credPrompt.error || credPrompt.submitting) ? 1 : 0);
|
|
5027
5066
|
const bottomBoxHeight = credPrompt ? credPromptHeight : composerBoxHeight;
|
|
5028
5067
|
const scrollHeight = Math.max(3, height - 5 - bottomBoxHeight - pendingRows);
|
|
@@ -5158,34 +5197,64 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5158
5197
|
cancelled = true;
|
|
5159
5198
|
};
|
|
5160
5199
|
}, []);
|
|
5161
|
-
const sendContent = useCallback(async (content,
|
|
5200
|
+
const sendContent = useCallback(async (content, staged, opts) => {
|
|
5162
5201
|
if (!rest) return;
|
|
5163
5202
|
const trimmed = content.trim();
|
|
5164
|
-
if (!trimmed &&
|
|
5203
|
+
if (!trimmed && staged.length === 0) return;
|
|
5165
5204
|
const echo = opts?.echo !== false;
|
|
5166
5205
|
const optimisticId = crypto.randomUUID();
|
|
5206
|
+
const stagedNames = staged.map((a) => a.fileName);
|
|
5167
5207
|
if (echo) setItems((prev) => [...prev, {
|
|
5168
5208
|
kind: "user",
|
|
5169
5209
|
id: optimisticId,
|
|
5170
|
-
text:
|
|
5210
|
+
text: trimmed,
|
|
5211
|
+
...stagedNames.length > 0 ? { attachments: stagedNames } : {}
|
|
5171
5212
|
}]);
|
|
5172
5213
|
const wasStreaming = runRef.current.kind === "streaming";
|
|
5173
5214
|
if (!wasStreaming) setRun({ kind: "sending" });
|
|
5174
5215
|
try {
|
|
5216
|
+
const attachmentIds = [];
|
|
5217
|
+
const restorable = [];
|
|
5218
|
+
const settled = await Promise.allSettled(staged.map((a) => uploadsRef.current.get(a.tempId)));
|
|
5219
|
+
for (const [i, result] of settled.entries()) {
|
|
5220
|
+
const row = staged[i];
|
|
5221
|
+
if (!row) continue;
|
|
5222
|
+
if (result.status === "fulfilled" && result.value) {
|
|
5223
|
+
attachmentIds.push(result.value.id);
|
|
5224
|
+
restorable.push({
|
|
5225
|
+
...row,
|
|
5226
|
+
status: "ready",
|
|
5227
|
+
errorText: null
|
|
5228
|
+
});
|
|
5229
|
+
} else {
|
|
5230
|
+
uploadsRef.current.delete(row.tempId);
|
|
5231
|
+
setItems((prev) => [...prev, {
|
|
5232
|
+
kind: "error",
|
|
5233
|
+
id: crypto.randomUUID(),
|
|
5234
|
+
text: `attachment failed: ${row.fileName}${result.status === "rejected" ? ` (${errorMessage(result.reason)})` : ""}`
|
|
5235
|
+
}]);
|
|
5236
|
+
}
|
|
5237
|
+
}
|
|
5238
|
+
if (!trimmed && attachmentIds.length === 0) {
|
|
5239
|
+
setItems((prev) => prev.filter((m) => m.id !== optimisticId));
|
|
5240
|
+
if (!wasStreaming) setRun({ kind: "idle" });
|
|
5241
|
+
return;
|
|
5242
|
+
}
|
|
5175
5243
|
const result = await rest.sendMessage({
|
|
5176
5244
|
agentId: agent.id,
|
|
5177
5245
|
conversationId,
|
|
5178
5246
|
content: trimmed,
|
|
5179
|
-
attachmentIds
|
|
5247
|
+
attachmentIds,
|
|
5180
5248
|
clientSurface: "tui"
|
|
5181
5249
|
});
|
|
5250
|
+
for (const row of restorable) uploadsRef.current.delete(row.tempId);
|
|
5182
5251
|
if (result.isNewConversation) setConversationId(result.conversationId);
|
|
5183
5252
|
if (result.steered) {
|
|
5184
5253
|
const directiveId = result.directive?.id;
|
|
5185
5254
|
if (directiveId) setItems((prev) => prev.map((m) => m.id === optimisticId ? {
|
|
5186
5255
|
kind: "pending-steer",
|
|
5187
5256
|
id: directiveId,
|
|
5188
|
-
text:
|
|
5257
|
+
text: trimmed
|
|
5189
5258
|
} : m));
|
|
5190
5259
|
const current = runRef.current;
|
|
5191
5260
|
if (!(current.kind === "streaming" && current.runId === result.runId)) attachToRun(result.runId);
|
|
@@ -5198,10 +5267,13 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5198
5267
|
id: crypto.randomUUID(),
|
|
5199
5268
|
text: errorMessage(err)
|
|
5200
5269
|
}]);
|
|
5201
|
-
if (echo)
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5270
|
+
if (echo) setInput((existing) => existing ? existing : trimmed);
|
|
5271
|
+
const restore = staged.filter((a) => uploadsRef.current.has(a.tempId));
|
|
5272
|
+
if (restore.length > 0) setAttachments((prev) => [...restore.map((a) => ({
|
|
5273
|
+
...a,
|
|
5274
|
+
status: "ready",
|
|
5275
|
+
errorText: null
|
|
5276
|
+
})), ...prev]);
|
|
5205
5277
|
if (!wasStreaming) setRun({ kind: "idle" });
|
|
5206
5278
|
}
|
|
5207
5279
|
}, [
|
|
@@ -5501,8 +5573,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5501
5573
|
]);
|
|
5502
5574
|
const submit = useCallback(() => {
|
|
5503
5575
|
const content = input.trim();
|
|
5504
|
-
const
|
|
5505
|
-
if (!content &&
|
|
5576
|
+
const staged = attachments.filter((a) => a.status !== "error");
|
|
5577
|
+
if (!content && staged.length === 0) return;
|
|
5506
5578
|
const routed = routeInput(content);
|
|
5507
5579
|
if (routed.kind !== "message") {
|
|
5508
5580
|
history.append(content);
|
|
@@ -5519,11 +5591,11 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5519
5591
|
setInput("");
|
|
5520
5592
|
composerRef.current?.clear();
|
|
5521
5593
|
setComposerRows(1);
|
|
5522
|
-
|
|
5523
|
-
sendContent(content,
|
|
5594
|
+
setAttachments([]);
|
|
5595
|
+
sendContent(content, staged);
|
|
5524
5596
|
}, [
|
|
5525
5597
|
input,
|
|
5526
|
-
|
|
5598
|
+
attachments,
|
|
5527
5599
|
sendContent,
|
|
5528
5600
|
history,
|
|
5529
5601
|
runLocalShell,
|
|
@@ -5537,38 +5609,58 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5537
5609
|
setInput(composer.plainText);
|
|
5538
5610
|
setComposerRows(Math.min(Math.max(composer.editorView.getTotalVirtualLineCount(), 1), maxComposerRows));
|
|
5539
5611
|
}, []);
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
data: image.data
|
|
5550
|
-
});
|
|
5551
|
-
setPending((prev) => [...prev, uploaded]);
|
|
5552
|
-
}
|
|
5553
|
-
} catch (err) {
|
|
5612
|
+
/**
|
|
5613
|
+
* Stage a pasted/dropped file in the composer tray and start its upload
|
|
5614
|
+
* immediately (the web composer's behavior). Submit awaits the in-flight
|
|
5615
|
+
* uploads and carries the resulting attachment ids; the composer never
|
|
5616
|
+
* blocks while an upload runs.
|
|
5617
|
+
*/
|
|
5618
|
+
const stageAttachment = useCallback(({ fileName, mediaType, data }) => {
|
|
5619
|
+
if (!rest) return;
|
|
5620
|
+
if (attachmentsRef.current.length >= maxAttachments) {
|
|
5554
5621
|
setItems((prev) => [...prev, {
|
|
5555
5622
|
kind: "error",
|
|
5556
5623
|
id: crypto.randomUUID(),
|
|
5557
|
-
text: `
|
|
5624
|
+
text: `attachment limit reached (${maxAttachments}) — ${fileName} skipped`
|
|
5558
5625
|
}]);
|
|
5559
|
-
|
|
5560
|
-
setUploading(false);
|
|
5626
|
+
return;
|
|
5561
5627
|
}
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5628
|
+
const tempId = crypto.randomUUID();
|
|
5629
|
+
setAttachments((prev) => [...prev, {
|
|
5630
|
+
tempId,
|
|
5631
|
+
fileName,
|
|
5632
|
+
sizeBytes: data.byteLength,
|
|
5633
|
+
status: "uploading",
|
|
5634
|
+
errorText: null
|
|
5635
|
+
}]);
|
|
5636
|
+
const promise = rest.uploadAttachment({
|
|
5637
|
+
agentId: agent.id,
|
|
5638
|
+
fileName,
|
|
5639
|
+
mediaType,
|
|
5640
|
+
data
|
|
5641
|
+
});
|
|
5642
|
+
uploadsRef.current.set(tempId, promise);
|
|
5643
|
+
promise.then(() => {
|
|
5644
|
+
setAttachments((prev) => prev.map((a) => a.tempId === tempId ? {
|
|
5645
|
+
...a,
|
|
5646
|
+
status: "ready"
|
|
5647
|
+
} : a));
|
|
5648
|
+
}).catch((err) => {
|
|
5649
|
+
setAttachments((prev) => prev.map((a) => a.tempId === tempId ? {
|
|
5650
|
+
...a,
|
|
5651
|
+
status: "error",
|
|
5652
|
+
errorText: errorMessage(err)
|
|
5653
|
+
} : a));
|
|
5654
|
+
});
|
|
5655
|
+
}, [rest, agent.id]);
|
|
5656
|
+
const stageFiles = useCallback((files) => {
|
|
5657
|
+
for (const file of files) stageAttachment(file);
|
|
5658
|
+
}, [stageAttachment]);
|
|
5567
5659
|
const pasteImage = useCallback(async () => {
|
|
5568
5660
|
const image = await readClipboardImage();
|
|
5569
5661
|
if (!image) return;
|
|
5570
|
-
|
|
5571
|
-
}, [
|
|
5662
|
+
stageAttachment(image);
|
|
5663
|
+
}, [stageAttachment]);
|
|
5572
5664
|
usePaste((event) => {
|
|
5573
5665
|
if (modelPickerOpen || themePickerOpen || helpOpen || credPromptOpen || grantPrompt) return;
|
|
5574
5666
|
const text = event.metadata?.kind === "binary" ? "" : decodePasteBytes(event.bytes);
|
|
@@ -5581,7 +5673,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5581
5673
|
case "binary-image": {
|
|
5582
5674
|
event.preventDefault();
|
|
5583
5675
|
const ext = route.mediaType.split("/")[1]?.split("+")[0] ?? "png";
|
|
5584
|
-
|
|
5676
|
+
stageFiles([{
|
|
5585
5677
|
fileName: `pasted-image-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.${ext}`,
|
|
5586
5678
|
mediaType: route.mediaType,
|
|
5587
5679
|
data: event.bytes
|
|
@@ -5594,9 +5686,9 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5594
5686
|
return;
|
|
5595
5687
|
case "dropped-paths":
|
|
5596
5688
|
event.preventDefault();
|
|
5597
|
-
|
|
5598
|
-
if (
|
|
5599
|
-
|
|
5689
|
+
resolveDroppedFiles(route.paths).then((files) => {
|
|
5690
|
+
if (files) {
|
|
5691
|
+
stageFiles(files);
|
|
5600
5692
|
return;
|
|
5601
5693
|
}
|
|
5602
5694
|
composerRef.current?.insertText(route.text);
|
|
@@ -5786,8 +5878,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5786
5878
|
pasteImage();
|
|
5787
5879
|
return;
|
|
5788
5880
|
}
|
|
5789
|
-
if (key.name === "x" && key.ctrl) {
|
|
5790
|
-
|
|
5881
|
+
if (key.name === "x" && key.ctrl || key.name === "backspace" && attachmentsRef.current.length > 0 && !composerRef.current?.plainText) {
|
|
5882
|
+
const last = attachmentsRef.current.at(-1);
|
|
5883
|
+
if (last) {
|
|
5884
|
+
uploadsRef.current.delete(last.tempId);
|
|
5885
|
+
setAttachments((prev) => prev.filter((a) => a.tempId !== last.tempId));
|
|
5886
|
+
}
|
|
5791
5887
|
return;
|
|
5792
5888
|
}
|
|
5793
5889
|
if (key.name === "l" && key.ctrl) openInBrowser();
|
|
@@ -5906,19 +6002,17 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5906
6002
|
marginTop: 1,
|
|
5907
6003
|
flexDirection: "column"
|
|
5908
6004
|
},
|
|
5909
|
-
children: [
|
|
5910
|
-
fg: theme.muted,
|
|
6005
|
+
children: [attachments.map((a) => /* @__PURE__ */ jsxs("text", {
|
|
6006
|
+
fg: a.status === "error" ? theme.error : theme.muted,
|
|
5911
6007
|
children: [
|
|
5912
6008
|
"📎 ",
|
|
5913
6009
|
a.fileName,
|
|
5914
6010
|
" (",
|
|
5915
6011
|
formatBytes(a.sizeBytes),
|
|
5916
|
-
")"
|
|
6012
|
+
") ",
|
|
6013
|
+
attachmentGlyph(a)
|
|
5917
6014
|
]
|
|
5918
|
-
}, a.
|
|
5919
|
-
fg: theme.dim,
|
|
5920
|
-
children: "uploading image…"
|
|
5921
|
-
}) : /* @__PURE__ */ jsx("text", {
|
|
6015
|
+
}, a.tempId)), /* @__PURE__ */ jsx("text", {
|
|
5922
6016
|
fg: theme.dim,
|
|
5923
6017
|
children: "ctrl+x remove last"
|
|
5924
6018
|
})]
|