@agent-native/toolkit 0.203.0-nightly-20261007093643 → 0.203.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/dist/agentkit.d.ts +2 -1
- package/dist/agentkit.d.ts.map +1 -1
- package/dist/agentkit.js +2 -1
- package/dist/agentkit.js.map +1 -1
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +5 -5
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/context.d.ts +1 -0
- package/dist/app/agentkit/react/context.d.ts.map +1 -1
- package/dist/app/agentkit/react/context.js +1 -0
- package/dist/app/agentkit/react/context.js.map +1 -1
- package/dist/app/agentkit/react/styles.css +6 -1
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +2 -1
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +1 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +13 -1
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +2 -2
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +9 -6
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/package.json +4 -4
- package/src/agentkit.ts +2 -0
- package/src/app/agentkit/react/components.interaction.spec.tsx +68 -0
- package/src/app/agentkit/react/components.tsx +11 -4
- package/src/app/agentkit/react/context.tsx +2 -0
- package/src/app/agentkit/react/styles.css +6 -1
- package/src/app/chat/AgentKitAssistantChat.tsx +4 -1
- package/src/composer/PromptComposer.spec.ts +80 -0
- package/src/composer/PromptComposer.tsx +15 -0
- package/src/composer/TiptapComposer.spec.ts +23 -0
- package/src/composer/TiptapComposer.tsx +18 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/toolkit",
|
|
3
|
-
"version": "0.203.0
|
|
3
|
+
"version": "0.203.0",
|
|
4
4
|
"description": "Reusable app-building UI and helpers for Agent-Native apps.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -673,7 +673,7 @@
|
|
|
673
673
|
"y-protocols": "^1.0.7",
|
|
674
674
|
"yjs": "^13.6.33",
|
|
675
675
|
"zod": "^4.3.6",
|
|
676
|
-
"@agent-native/agentkit": "^0.203.0
|
|
676
|
+
"@agent-native/agentkit": "^0.203.0"
|
|
677
677
|
},
|
|
678
678
|
"devDependencies": {
|
|
679
679
|
"@excalidraw/excalidraw": "0.18.1",
|
|
@@ -697,10 +697,10 @@
|
|
|
697
697
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
698
698
|
"vite": "8.1.0",
|
|
699
699
|
"vitest": "^4.1.5",
|
|
700
|
-
"@agent-native/core": "^0.203.0
|
|
700
|
+
"@agent-native/core": "^0.203.0"
|
|
701
701
|
},
|
|
702
702
|
"peerDependencies": {
|
|
703
|
-
"@agent-native/core": "0.
|
|
703
|
+
"@agent-native/core": ">=0.196.0",
|
|
704
704
|
"@excalidraw/excalidraw": ">=0.18",
|
|
705
705
|
"@excalidraw/mermaid-to-excalidraw": ">=2",
|
|
706
706
|
"@tanstack/react-query": "^5.99.2",
|
package/src/agentkit.ts
CHANGED
|
@@ -3,6 +3,7 @@ export {
|
|
|
3
3
|
AgentSuggestionBar,
|
|
4
4
|
agentSuggestionPrompt,
|
|
5
5
|
} from "./composer/AgentSuggestionBar.js";
|
|
6
|
+
export { formatAttachmentError } from "./composer/attachment-accept.js";
|
|
6
7
|
export { MessageQueueDrawer } from "./composer/MessageQueueDrawer.js";
|
|
7
8
|
export {
|
|
8
9
|
ComposerContextMenu,
|
|
@@ -21,6 +22,7 @@ export {
|
|
|
21
22
|
type ComposerContextPickerFooterAction,
|
|
22
23
|
} from "./composer/ComposerContextMenu.js";
|
|
23
24
|
export {
|
|
25
|
+
createChatAttachmentAdapter,
|
|
24
26
|
PromptComposer,
|
|
25
27
|
type PromptComposerFile,
|
|
26
28
|
type PromptComposerProps,
|
|
@@ -870,6 +870,74 @@ describe("AgentKitChat interactions", () => {
|
|
|
870
870
|
}
|
|
871
871
|
});
|
|
872
872
|
|
|
873
|
+
it("attaches dropped SVGs and names rejected drops without the accept list", async () => {
|
|
874
|
+
const transport: AgentTransport = {
|
|
875
|
+
capabilities: { uploads: true },
|
|
876
|
+
async startRun() {
|
|
877
|
+
return { runId: "run-svg" };
|
|
878
|
+
},
|
|
879
|
+
async *subscribeToRun() {},
|
|
880
|
+
async cancelRun() {},
|
|
881
|
+
};
|
|
882
|
+
const client = new AgentKitClient({ transport });
|
|
883
|
+
const onAttachmentError = vi.fn();
|
|
884
|
+
const container = document.createElement("div");
|
|
885
|
+
document.body.appendChild(container);
|
|
886
|
+
const root = createRoot(container);
|
|
887
|
+
const actEnvironment = globalThis as typeof globalThis & {
|
|
888
|
+
IS_REACT_ACT_ENVIRONMENT?: boolean;
|
|
889
|
+
};
|
|
890
|
+
const previousActEnvironment = actEnvironment.IS_REACT_ACT_ENVIRONMENT;
|
|
891
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
892
|
+
const dropOnChat = async (file: File) => {
|
|
893
|
+
const drop = new Event("drop", { bubbles: true, cancelable: true });
|
|
894
|
+
Object.defineProperty(drop, "dataTransfer", {
|
|
895
|
+
value: { types: ["Files"], files: [file], dropEffect: "none" },
|
|
896
|
+
});
|
|
897
|
+
await act(async () => {
|
|
898
|
+
container.querySelector(".agentkit-chat")?.dispatchEvent(drop);
|
|
899
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
900
|
+
});
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
try {
|
|
904
|
+
await act(async () => {
|
|
905
|
+
root.render(
|
|
906
|
+
<AgentKitProvider controller={client} threadId="thread-svg">
|
|
907
|
+
<AgentKitChat composerProps={{ onAttachmentError }} />
|
|
908
|
+
</AgentKitProvider>,
|
|
909
|
+
);
|
|
910
|
+
await Promise.resolve();
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
await dropOnChat(
|
|
914
|
+
new File(['<svg xmlns="http://www.w3.org/2000/svg"/>'], "logo.svg", {
|
|
915
|
+
type: "image/svg+xml",
|
|
916
|
+
}),
|
|
917
|
+
);
|
|
918
|
+
expect(container.textContent).toContain("logo.svg");
|
|
919
|
+
expect(onAttachmentError).not.toHaveBeenCalled();
|
|
920
|
+
|
|
921
|
+
await dropOnChat(
|
|
922
|
+
new File(["zip"], "archive.zip", { type: "application/zip" }),
|
|
923
|
+
);
|
|
924
|
+
expect(onAttachmentError).toHaveBeenCalledWith(
|
|
925
|
+
"archive.zip: Could not add the dropped file. Try a different format.",
|
|
926
|
+
);
|
|
927
|
+
} finally {
|
|
928
|
+
await act(async () => {
|
|
929
|
+
root.unmount();
|
|
930
|
+
await Promise.resolve();
|
|
931
|
+
});
|
|
932
|
+
container.remove();
|
|
933
|
+
if (previousActEnvironment === undefined) {
|
|
934
|
+
delete actEnvironment.IS_REACT_ACT_ENVIRONMENT;
|
|
935
|
+
} else {
|
|
936
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = previousActEnvironment;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
873
941
|
it("disables transcript file drops when the host disables uploads", async () => {
|
|
874
942
|
const transport: AgentTransport = {
|
|
875
943
|
capabilities: { uploads: true },
|
|
@@ -68,6 +68,8 @@ import {
|
|
|
68
68
|
Surface,
|
|
69
69
|
TextField,
|
|
70
70
|
agentSuggestionPrompt,
|
|
71
|
+
createChatAttachmentAdapter,
|
|
72
|
+
formatAttachmentError,
|
|
71
73
|
type PromptComposerFile,
|
|
72
74
|
type PromptComposerProps,
|
|
73
75
|
type TiptapComposerHandle,
|
|
@@ -4065,6 +4067,10 @@ export function AgentKitComposer({
|
|
|
4065
4067
|
}, []);
|
|
4066
4068
|
const [uncontrolledMode, setUncontrolledMode] = useState(defaultMode);
|
|
4067
4069
|
const [attachmentError, setAttachmentError] = useState<string | null>(null);
|
|
4070
|
+
const composerAttachmentAdapter = useMemo(
|
|
4071
|
+
() => attachmentAdapter ?? createChatAttachmentAdapter(),
|
|
4072
|
+
[attachmentAdapter],
|
|
4073
|
+
);
|
|
4068
4074
|
const executionMode = mode ?? uncontrolledMode;
|
|
4069
4075
|
const active = hasActiveRuns(thread);
|
|
4070
4076
|
const composerInitialText = editingMessage
|
|
@@ -4095,12 +4101,13 @@ export function AgentKitComposer({
|
|
|
4095
4101
|
await composer.addAttachment(file);
|
|
4096
4102
|
setAttachmentError(null);
|
|
4097
4103
|
} catch (error) {
|
|
4098
|
-
|
|
4099
|
-
|
|
4104
|
+
reportAttachmentError(
|
|
4105
|
+
`${file.name}: ${formatAttachmentError(error, labels.dropFileFailed)}`,
|
|
4106
|
+
);
|
|
4100
4107
|
throw error;
|
|
4101
4108
|
}
|
|
4102
4109
|
},
|
|
4103
|
-
[labels.error, reportAttachmentError],
|
|
4110
|
+
[labels.dropFileFailed, labels.error, reportAttachmentError],
|
|
4104
4111
|
);
|
|
4105
4112
|
useEffect(() => {
|
|
4106
4113
|
const receiveAttachments = (event: Event) => {
|
|
@@ -4494,7 +4501,7 @@ export function AgentKitComposer({
|
|
|
4494
4501
|
onInspectContextItem={onInspectContextItem}
|
|
4495
4502
|
onRetryContextItem={onRetryContextItem}
|
|
4496
4503
|
contextMenuItems={contextMenuItems}
|
|
4497
|
-
attachmentAdapter={
|
|
4504
|
+
attachmentAdapter={composerAttachmentAdapter}
|
|
4498
4505
|
inlineTextAttachments={inlineTextAttachments}
|
|
4499
4506
|
rootClassName="agentkit-composer"
|
|
4500
4507
|
className={
|
|
@@ -274,6 +274,7 @@ export interface AgentKitLabels {
|
|
|
274
274
|
imagePreview: string;
|
|
275
275
|
closePreview: string;
|
|
276
276
|
dropFilesToAttach: string;
|
|
277
|
+
dropFileFailed: string;
|
|
277
278
|
scrollToBottom: string;
|
|
278
279
|
formatTimestamp?: (createdAt: string) => string;
|
|
279
280
|
error: string;
|
|
@@ -384,6 +385,7 @@ export const defaultAgentKitLabels: AgentKitLabels = {
|
|
|
384
385
|
imagePreview: "Image preview",
|
|
385
386
|
closePreview: "Close preview",
|
|
386
387
|
dropFilesToAttach: "Drop files to attach",
|
|
388
|
+
dropFileFailed: "Could not add the dropped file. Try a different format.",
|
|
387
389
|
scrollToBottom: "Scroll to bottom",
|
|
388
390
|
error: "Something went wrong",
|
|
389
391
|
renderError: "This content couldn’t be displayed.",
|
|
@@ -1715,11 +1715,16 @@ button.agentkit-object {
|
|
|
1715
1715
|
|
|
1716
1716
|
.agentkit-composer-error {
|
|
1717
1717
|
display: flex;
|
|
1718
|
-
align-items:
|
|
1718
|
+
align-items: flex-start;
|
|
1719
1719
|
gap: 0.375rem;
|
|
1720
1720
|
padding: 0.375rem 0.625rem 0;
|
|
1721
1721
|
}
|
|
1722
1722
|
|
|
1723
|
+
.agentkit-composer-error > span {
|
|
1724
|
+
min-inline-size: 0;
|
|
1725
|
+
overflow-wrap: anywhere;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1723
1728
|
.agentkit-file {
|
|
1724
1729
|
display: inline-flex;
|
|
1725
1730
|
align-items: center;
|
|
@@ -887,6 +887,7 @@ export const AgentKitAssistantChat = forwardRef<
|
|
|
887
887
|
imagePreview: t("agentChat.composer.imagePreview"),
|
|
888
888
|
closePreview: t("agentChat.composer.closePreview"),
|
|
889
889
|
dropFilesToAttach: t("agentChat.composer.dropToAttach"),
|
|
890
|
+
dropFileFailed: t("agentChat.composer.droppedFileError"),
|
|
890
891
|
scrollToBottom: t("agentChat.composer.scrollToBottom"),
|
|
891
892
|
formatTimestamp: (createdAt: string) => {
|
|
892
893
|
const date = new Date(createdAt);
|
|
@@ -4239,7 +4240,9 @@ function AgentKitComposerSurface({
|
|
|
4239
4240
|
className="mx-3 mb-1.5 flex shrink-0 items-start gap-2 rounded-md border border-border bg-muted/70 px-3 py-2 text-xs text-foreground shadow-sm"
|
|
4240
4241
|
>
|
|
4241
4242
|
<IconAlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
4242
|
-
<span className="flex-1 leading-snug">
|
|
4243
|
+
<span className="min-w-0 flex-1 break-words leading-snug">
|
|
4244
|
+
{composerError}
|
|
4245
|
+
</span>
|
|
4243
4246
|
<button
|
|
4244
4247
|
type="button"
|
|
4245
4248
|
aria-label={t("agentChat.common.dismissError")}
|
|
@@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
buildPromptComposerSubmission,
|
|
9
|
+
createChatAttachmentAdapter,
|
|
9
10
|
PromptComposer,
|
|
10
11
|
resolveComposerModelStatusChecksEnabled,
|
|
11
12
|
shouldGateComposerForEngine,
|
|
@@ -234,3 +235,82 @@ describe("PromptComposer scoped runtime", () => {
|
|
|
234
235
|
expect(attachedFiles).toHaveLength(0);
|
|
235
236
|
});
|
|
236
237
|
});
|
|
238
|
+
|
|
239
|
+
describe("PromptComposer attachment policy", () => {
|
|
240
|
+
it("keeps SVGs out of standalone prompts while chat stages them as documents", async () => {
|
|
241
|
+
await act(async () => {
|
|
242
|
+
root.render(
|
|
243
|
+
React.createElement(PromptComposer, {
|
|
244
|
+
attachmentsEnabled: true,
|
|
245
|
+
includeDefaultSlashSkills: false,
|
|
246
|
+
onSubmit: () => {},
|
|
247
|
+
plusMenuMode: "upload-only",
|
|
248
|
+
showModelSelector: false,
|
|
249
|
+
voiceEnabled: false,
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
253
|
+
});
|
|
254
|
+
const standaloneAccept = container
|
|
255
|
+
.querySelector<HTMLInputElement>('input[type="file"]')
|
|
256
|
+
?.getAttribute("accept")
|
|
257
|
+
?.split(",");
|
|
258
|
+
expect(standaloneAccept).toContain(".pdf");
|
|
259
|
+
expect(standaloneAccept).not.toContain(".svg");
|
|
260
|
+
|
|
261
|
+
const chatAdapter = createChatAttachmentAdapter();
|
|
262
|
+
expect(chatAdapter.accept.split(",")).toEqual(
|
|
263
|
+
expect.arrayContaining(["image/svg+xml", ".svg", ".png", ".pdf"]),
|
|
264
|
+
);
|
|
265
|
+
await expect(
|
|
266
|
+
chatAdapter.add({
|
|
267
|
+
file: new File(["<svg/>"], "logo.svg", { type: "image/svg+xml" }),
|
|
268
|
+
}),
|
|
269
|
+
).resolves.toMatchObject({
|
|
270
|
+
type: "document",
|
|
271
|
+
name: "logo.svg",
|
|
272
|
+
contentType: "image/svg+xml",
|
|
273
|
+
});
|
|
274
|
+
await expect(
|
|
275
|
+
chatAdapter.add({
|
|
276
|
+
file: new File(["png"], "logo.png", { type: "image/png" }),
|
|
277
|
+
}),
|
|
278
|
+
).resolves.toMatchObject({ type: "image", name: "logo.png" });
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("shows why a file was rejected when the host passes no error handler", async () => {
|
|
282
|
+
await act(async () => {
|
|
283
|
+
root.render(
|
|
284
|
+
React.createElement(PromptComposer, {
|
|
285
|
+
attachmentsEnabled: true,
|
|
286
|
+
includeDefaultSlashSkills: false,
|
|
287
|
+
onSubmit: () => {},
|
|
288
|
+
plusMenuMode: "upload-only",
|
|
289
|
+
showModelSelector: false,
|
|
290
|
+
voiceEnabled: false,
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
294
|
+
});
|
|
295
|
+
const editor = container.querySelector<HTMLElement>(
|
|
296
|
+
".agent-composer-prosemirror",
|
|
297
|
+
);
|
|
298
|
+
expect(editor).not.toBeNull();
|
|
299
|
+
const pasteEvent = new Event("paste", { bubbles: true, cancelable: true });
|
|
300
|
+
Object.defineProperty(pasteEvent, "clipboardData", {
|
|
301
|
+
value: {
|
|
302
|
+
files: [new File(["<svg/>"], "logo.svg", { type: "image/svg+xml" })],
|
|
303
|
+
getData: () => "",
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await act(async () => {
|
|
308
|
+
editor?.dispatchEvent(pasteEvent);
|
|
309
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
expect(container.querySelector('[role="alert"]')?.textContent).toBe(
|
|
313
|
+
"Could not attach the pasted image. Try a different format.",
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
@@ -33,6 +33,7 @@ import { cn } from "../utils.js";
|
|
|
33
33
|
import { AgentComposerFrame } from "./AgentComposerFrame.js";
|
|
34
34
|
import { IMAGE_ATTACHMENT_ACCEPT } from "./attachment-accept.js";
|
|
35
35
|
import {
|
|
36
|
+
CHAT_DOCUMENT_ATTACHMENT_ACCEPT,
|
|
36
37
|
PROMPT_DOCUMENT_ATTACHMENT_ACCEPT,
|
|
37
38
|
TextAttachmentAdapter,
|
|
38
39
|
} from "./attachment-accept.js";
|
|
@@ -314,6 +315,20 @@ class RasterImageAttachmentAdapter extends SimpleImageAttachmentAdapter {
|
|
|
314
315
|
}
|
|
315
316
|
}
|
|
316
317
|
|
|
318
|
+
// SVGs never match the raster adapter, so agent chat stages them as reference
|
|
319
|
+
// documents rather than images a model would try to read as pixels.
|
|
320
|
+
class ChatDocumentAttachmentAdapter extends BinaryDocumentAttachmentAdapter {
|
|
321
|
+
public accept = CHAT_DOCUMENT_ATTACHMENT_ACCEPT;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function createChatAttachmentAdapter(): AttachmentAdapter {
|
|
325
|
+
return new CompositeAttachmentAdapter([
|
|
326
|
+
new RasterImageAttachmentAdapter(),
|
|
327
|
+
new ChatDocumentAttachmentAdapter(),
|
|
328
|
+
new TextAttachmentAdapter(),
|
|
329
|
+
]);
|
|
330
|
+
}
|
|
331
|
+
|
|
317
332
|
function isInlineableTextFile(file: File): boolean {
|
|
318
333
|
if (file.type.startsWith("text/")) return true;
|
|
319
334
|
if (
|
|
@@ -2554,6 +2554,29 @@ describe("createTiptapComposerExtensions", () => {
|
|
|
2554
2554
|
});
|
|
2555
2555
|
});
|
|
2556
2556
|
|
|
2557
|
+
it("names a rejected image drop by the file the user dropped", async () => {
|
|
2558
|
+
const file = new File(["<svg/>"], "logo.svg", { type: "image/svg+xml" });
|
|
2559
|
+
const added: File[] = [];
|
|
2560
|
+
const onError = vi.fn();
|
|
2561
|
+
handleComposerFileDrop({
|
|
2562
|
+
event: {
|
|
2563
|
+
dataTransfer: { files: [file] },
|
|
2564
|
+
preventDefault: () => {},
|
|
2565
|
+
stopPropagation: () => {},
|
|
2566
|
+
} as unknown as DragEvent,
|
|
2567
|
+
addAttachment: async (attachment) => {
|
|
2568
|
+
added.push(attachment);
|
|
2569
|
+
throw new Error("File type image/svg+xml is not accepted.");
|
|
2570
|
+
},
|
|
2571
|
+
onError,
|
|
2572
|
+
});
|
|
2573
|
+
|
|
2574
|
+
await vi.waitFor(() => {
|
|
2575
|
+
expect(onError).toHaveBeenCalledWith(expect.any(Error), "logo.svg");
|
|
2576
|
+
});
|
|
2577
|
+
expect(added[0]?.name).toMatch(/^\d+-[a-z0-9]+-logo\.svg$/);
|
|
2578
|
+
});
|
|
2579
|
+
|
|
2557
2580
|
it("caps the model picker height without forcing empty vertical space", () => {
|
|
2558
2581
|
expect(MODEL_SELECTOR_POPOVER_STYLE).toMatchObject({
|
|
2559
2582
|
fontSize: 13,
|
|
@@ -770,16 +770,15 @@ export function handleComposerFileDrop(options: {
|
|
|
770
770
|
options.event.preventDefault();
|
|
771
771
|
options.event.stopPropagation();
|
|
772
772
|
if (options.attachmentsEnabled === false) return true;
|
|
773
|
-
const attachments = droppedFiles.map(uniquifyComposerImageFile);
|
|
774
773
|
let errorReported = false;
|
|
775
774
|
void Promise.all(
|
|
776
|
-
|
|
775
|
+
droppedFiles.map(async (droppedFile) => {
|
|
777
776
|
try {
|
|
778
|
-
await options.addAttachment(
|
|
777
|
+
await options.addAttachment(uniquifyComposerImageFile(droppedFile));
|
|
779
778
|
} catch (error) {
|
|
780
779
|
if (errorReported) return;
|
|
781
780
|
errorReported = true;
|
|
782
|
-
options.onError?.(error,
|
|
781
|
+
options.onError?.(error, droppedFile.name);
|
|
783
782
|
}
|
|
784
783
|
}),
|
|
785
784
|
);
|
|
@@ -1223,8 +1222,8 @@ export interface TiptapComposerProps {
|
|
|
1223
1222
|
interceptBuildRequestsForBuilder?: boolean;
|
|
1224
1223
|
/**
|
|
1225
1224
|
* Called when a drag-drop or paste attachment fails (e.g. unsupported format,
|
|
1226
|
-
* size cap)
|
|
1227
|
-
*
|
|
1225
|
+
* size cap) so the host can show it in its own surface. Without it, the
|
|
1226
|
+
* composer shows the message inline.
|
|
1228
1227
|
*/
|
|
1229
1228
|
onAttachmentError?: (message: string) => void;
|
|
1230
1229
|
}
|
|
@@ -2873,8 +2872,10 @@ export function TiptapComposer({
|
|
|
2873
2872
|
// Refs for values accessed in handleKeyDown (ProseMirror doesn't re-bind)
|
|
2874
2873
|
const popoverStateRef = useRef<PopoverState>(null);
|
|
2875
2874
|
const composingRef = useRef(false);
|
|
2875
|
+
const [attachmentError, setAttachmentError] = useState<string | null>(null);
|
|
2876
2876
|
const onAttachmentErrorRef = useRef(onAttachmentError);
|
|
2877
|
-
|
|
2877
|
+
// Many standalone prompts pass no handler; a rejected file must still say so.
|
|
2878
|
+
onAttachmentErrorRef.current = onAttachmentError ?? setAttachmentError;
|
|
2878
2879
|
const onSubmitRef = useRef(onSubmit);
|
|
2879
2880
|
onSubmitRef.current = onSubmit;
|
|
2880
2881
|
const execModeRef = useRef(execMode);
|
|
@@ -3101,6 +3102,7 @@ export function TiptapComposer({
|
|
|
3101
3102
|
}, [cleanStaleAttachments, composerRuntime]);
|
|
3102
3103
|
const addAttachmentForCurrentScope = useCallback(
|
|
3103
3104
|
(file: File) => {
|
|
3105
|
+
setAttachmentError(null);
|
|
3104
3106
|
const scopeGeneration = draftScopeGenerationRef.current;
|
|
3105
3107
|
const submissionBarrier = attachmentSubmissionBarrierRef.current;
|
|
3106
3108
|
let resolveOperation!: () => void;
|
|
@@ -3297,6 +3299,7 @@ export function TiptapComposer({
|
|
|
3297
3299
|
// Drive the send button's enabled state from the actual editor contents;
|
|
3298
3300
|
// the composer runtime is only synced on submit, so its isEmpty lags.
|
|
3299
3301
|
setEditorHasText(composerDocumentHasContent(ed.state.doc));
|
|
3302
|
+
setAttachmentError(null);
|
|
3300
3303
|
onTextChangeRef.current?.(ed.getText({ blockSeparator: "\n" }).trim());
|
|
3301
3304
|
setReferenceRevision((revision) => revision + 1);
|
|
3302
3305
|
|
|
@@ -5871,6 +5874,14 @@ export function TiptapComposer({
|
|
|
5871
5874
|
{contextSubmissionError}
|
|
5872
5875
|
</p>
|
|
5873
5876
|
) : null}
|
|
5877
|
+
{attachmentError ? (
|
|
5878
|
+
<p
|
|
5879
|
+
role="alert"
|
|
5880
|
+
className="break-words px-2.5 pt-2 text-xs text-destructive"
|
|
5881
|
+
>
|
|
5882
|
+
{attachmentError}
|
|
5883
|
+
</p>
|
|
5884
|
+
) : null}
|
|
5874
5885
|
<div
|
|
5875
5886
|
data-agent-composer-variant={layoutVariant}
|
|
5876
5887
|
data-agent-composer-slot="editor-wrap"
|