@agent-native/toolkit 0.198.4 → 0.198.6
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/app/agentkit/react/components.d.ts +6 -3
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +98 -68
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/styles.css +36 -24
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +32 -15
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/AgentPanel.d.ts +1 -1
- package/dist/app/chat/AgentPanel.d.ts.map +1 -1
- package/dist/app/chat/AgentPanel.js +11 -38
- package/dist/app/chat/AgentPanel.js.map +1 -1
- package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
- package/dist/app/chat/AgentSidebar.js +3 -11
- package/dist/app/chat/AgentSidebar.js.map +1 -1
- package/dist/app/chat/chat/tool-call-display.d.ts +2 -1
- package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
- package/dist/app/chat/chat/tool-call-display.js +83 -55
- package/dist/app/chat/chat/tool-call-display.js.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +1 -0
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +1 -0
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +1 -0
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +1 -0
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +1 -0
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +1 -0
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +1 -0
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +1 -0
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +1 -0
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +1 -0
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +1 -0
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +1 -0
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +3 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +2 -2
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +4 -1
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +32 -25
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/ui/sonner.js +1 -1
- package/dist/ui/sonner.js.map +1 -1
- package/package.json +3 -3
- package/src/app/agentkit/react/components.interaction.spec.tsx +93 -0
- package/src/app/agentkit/react/components.spec.tsx +1 -2
- package/src/app/agentkit/react/components.tsx +185 -132
- package/src/app/agentkit/react/composer-context.spec.tsx +7 -0
- package/src/app/agentkit/react/lifecycle.spec.tsx +556 -39
- package/src/app/agentkit/react/styles.css +36 -24
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +60 -1
- package/src/app/chat/AgentKitAssistantChat.tsx +45 -14
- package/src/app/chat/AgentPanel.overlay.spec.tsx +28 -4
- package/src/app/chat/AgentPanel.tsx +51 -168
- package/src/app/chat/AgentSidebar.spec.tsx +19 -19
- package/src/app/chat/AgentSidebar.tsx +59 -94
- package/src/app/chat/chat/tool-call-display.spec.tsx +371 -15
- package/src/app/chat/chat/tool-call-display.tsx +154 -80
- package/src/app/i18n/catalogs/ar-SA.ts +1 -0
- package/src/app/i18n/catalogs/de-DE.ts +1 -0
- package/src/app/i18n/catalogs/en-US.ts +1 -0
- package/src/app/i18n/catalogs/es-ES.ts +1 -0
- package/src/app/i18n/catalogs/fr-FR.ts +1 -0
- package/src/app/i18n/catalogs/hi-IN.ts +1 -0
- package/src/app/i18n/catalogs/ja-JP.ts +1 -0
- package/src/app/i18n/catalogs/ko-KR.ts +1 -0
- package/src/app/i18n/catalogs/pt-BR.ts +1 -0
- package/src/app/i18n/catalogs/zh-CN.ts +1 -0
- package/src/app/i18n/catalogs/zh-TW.ts +1 -0
- package/src/app-shell/sidebar-overflow.spec.tsx +2 -1
- package/src/composer/PromptComposer.context.spec.tsx +28 -15
- package/src/composer/PromptComposer.tsx +7 -0
- package/src/composer/TiptapComposer.spec.ts +40 -6
- package/src/composer/TiptapComposer.tsx +89 -89
- package/src/ui/sonner.spec.ts +15 -0
- package/src/ui/sonner.tsx +1 -1
|
@@ -791,19 +791,16 @@ describe("controlled composer context", () => {
|
|
|
791
791
|
expect(option).toBeDefined();
|
|
792
792
|
await act(async () => option.click());
|
|
793
793
|
expect(onSelect).toHaveBeenCalledOnce();
|
|
794
|
+
const contextRow = container.querySelector('[data-context-key="brief"]');
|
|
795
|
+
expect(contextRow?.querySelectorAll("button")).toHaveLength(1);
|
|
794
796
|
await act(async () => {
|
|
795
|
-
container
|
|
796
|
-
.querySelector<HTMLButtonElement>(
|
|
797
|
-
'button[aria-label="Retry Brief context"]',
|
|
798
|
-
)!
|
|
799
|
-
.click();
|
|
800
797
|
container
|
|
801
798
|
.querySelector<HTMLButtonElement>(
|
|
802
799
|
'button[aria-label="Remove Brief context"]',
|
|
803
800
|
)!
|
|
804
801
|
.click();
|
|
805
802
|
});
|
|
806
|
-
expect(onRetry).
|
|
803
|
+
expect(onRetry).not.toHaveBeenCalled();
|
|
807
804
|
expect(onRemove).toHaveBeenCalledWith("brief");
|
|
808
805
|
expect(onDisabledClick).not.toHaveBeenCalled();
|
|
809
806
|
const send = container.querySelector<HTMLButtonElement>(
|
|
@@ -1456,7 +1453,7 @@ describe("controlled composer context", () => {
|
|
|
1456
1453
|
expect(remove).not.toHaveBeenCalled();
|
|
1457
1454
|
},
|
|
1458
1455
|
);
|
|
1459
|
-
it("renders context
|
|
1456
|
+
it("renders a noninteractive context chip with only removal and blocks submission until ready", async () => {
|
|
1460
1457
|
const onSubmit = vi.fn();
|
|
1461
1458
|
const onRemoveContextItem = vi.fn();
|
|
1462
1459
|
const onInspectContextItem = vi.fn();
|
|
@@ -1522,25 +1519,41 @@ describe("controlled composer context", () => {
|
|
|
1522
1519
|
await pressEnter();
|
|
1523
1520
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
1524
1521
|
const contextRow = container.querySelector('[data-context-key="brief"]')!;
|
|
1522
|
+
expect(contextRow.className).toContain("py-0.5");
|
|
1523
|
+
expect(contextRow.querySelector("span")?.textContent).toBe("Brief");
|
|
1524
|
+
expect(contextRow.querySelectorAll("button")).toHaveLength(1);
|
|
1525
|
+
expect(
|
|
1526
|
+
contextRow.querySelector('[aria-label="Context failed"]'),
|
|
1527
|
+
).not.toBeNull();
|
|
1528
|
+
const retryContext = container.querySelector<HTMLButtonElement>(
|
|
1529
|
+
'button[aria-label="Retry Brief context"]',
|
|
1530
|
+
);
|
|
1531
|
+
expect(retryContext).not.toBeNull();
|
|
1532
|
+
expect(contextRow.contains(retryContext)).toBe(false);
|
|
1533
|
+
await act(async () => retryContext!.click());
|
|
1534
|
+
expect(onRetryContextItem).toHaveBeenCalledWith("brief");
|
|
1525
1535
|
await act(async () => {
|
|
1526
|
-
contextRow.querySelector<HTMLButtonElement>("button")!.click();
|
|
1527
|
-
contextRow
|
|
1528
|
-
.querySelector<HTMLButtonElement>(
|
|
1529
|
-
'button[aria-label="Retry Brief context"]',
|
|
1530
|
-
)!
|
|
1531
|
-
.click();
|
|
1532
1536
|
contextRow
|
|
1533
1537
|
.querySelector<HTMLButtonElement>(
|
|
1534
1538
|
'button[aria-label="Remove Brief context"]',
|
|
1535
1539
|
)!
|
|
1536
1540
|
.click();
|
|
1537
1541
|
});
|
|
1538
|
-
expect(onInspectContextItem).
|
|
1539
|
-
expect(onRetryContextItem).
|
|
1542
|
+
expect(onInspectContextItem).not.toHaveBeenCalled();
|
|
1543
|
+
expect(onRetryContextItem).toHaveBeenCalledOnce();
|
|
1540
1544
|
expect(onRemoveContextItem).toHaveBeenCalledWith("brief");
|
|
1541
1545
|
expect(
|
|
1542
1546
|
container.querySelector('[data-context-key="brief"]'),
|
|
1543
1547
|
).not.toBeNull();
|
|
1548
|
+
item.removable = false;
|
|
1549
|
+
await render();
|
|
1550
|
+
const nonremovableContextRow = container.querySelector(
|
|
1551
|
+
'[data-context-key="brief"]',
|
|
1552
|
+
)!;
|
|
1553
|
+
expect(nonremovableContextRow.querySelectorAll("button")).toHaveLength(0);
|
|
1554
|
+
expect(
|
|
1555
|
+
container.querySelector('button[aria-label="Retry Brief context"]'),
|
|
1556
|
+
).not.toBeNull();
|
|
1544
1557
|
item.status = "ready";
|
|
1545
1558
|
await render();
|
|
1546
1559
|
await clickSend();
|
|
@@ -106,6 +106,7 @@ export interface PromptComposerProps {
|
|
|
106
106
|
) => void | Promise<void>;
|
|
107
107
|
/** Return false to stop a submit before it reaches the host runtime. */
|
|
108
108
|
onBeforeSubmit?: () => boolean | Promise<boolean>;
|
|
109
|
+
onSubmissionPendingChange?: (pending: boolean) => void;
|
|
109
110
|
/** Scope where a failed submission should be recovered after the host forks. */
|
|
110
111
|
getSubmitFailureDraftScope?: () => string | null;
|
|
111
112
|
/** Handle file paste/drop errors in the host chat surface. */
|
|
@@ -118,6 +119,8 @@ export interface PromptComposerProps {
|
|
|
118
119
|
disabled?: boolean;
|
|
119
120
|
/** Block all submission paths while allowing draft, file, and context staging. */
|
|
120
121
|
submissionDisabled?: boolean;
|
|
122
|
+
/** Disable only the send control while the submission is being accepted. */
|
|
123
|
+
sendButtonDisabled?: boolean;
|
|
121
124
|
/** Prevent submission while preserving editor focus and draft entry. */
|
|
122
125
|
submitting?: boolean;
|
|
123
126
|
/** Present the primary action as queueing instead of immediate send. */
|
|
@@ -608,6 +611,7 @@ function PromptComposerInner({
|
|
|
608
611
|
ariaLabel,
|
|
609
612
|
disabled,
|
|
610
613
|
submissionDisabled,
|
|
614
|
+
sendButtonDisabled,
|
|
611
615
|
submitting,
|
|
612
616
|
willQueue = false,
|
|
613
617
|
onDisabledClick,
|
|
@@ -680,6 +684,7 @@ function PromptComposerInner({
|
|
|
680
684
|
imageModelMenu,
|
|
681
685
|
composerRef,
|
|
682
686
|
onBeforeSubmit,
|
|
687
|
+
onSubmissionPendingChange,
|
|
683
688
|
getSubmitFailureDraftScope,
|
|
684
689
|
onAttachmentError,
|
|
685
690
|
interceptBuildRequestsForBuilder,
|
|
@@ -890,6 +895,7 @@ function PromptComposerInner({
|
|
|
890
895
|
disabled={disabled}
|
|
891
896
|
contextControlsDisabled={engineSubmissionBlocked}
|
|
892
897
|
submissionDisabled={submissionDisabled || engineSubmissionBlocked}
|
|
898
|
+
sendButtonDisabled={sendButtonDisabled}
|
|
893
899
|
submitting={submitting}
|
|
894
900
|
willQueue={willQueue}
|
|
895
901
|
maxDocumentAttachmentBytes={maxDocumentAttachmentBytes}
|
|
@@ -899,6 +905,7 @@ function PromptComposerInner({
|
|
|
899
905
|
initialTextKey={initialTextKey}
|
|
900
906
|
onSubmit={handleSubmit}
|
|
901
907
|
onBeforeSubmit={onBeforeSubmit}
|
|
908
|
+
onSubmissionPendingChange={onSubmissionPendingChange}
|
|
902
909
|
getSubmitFailureDraftScope={getSubmitFailureDraftScope}
|
|
903
910
|
onAttachmentError={onAttachmentError}
|
|
904
911
|
interceptBuildRequestsForBuilder={interceptBuildRequestsForBuilder}
|
|
@@ -1581,10 +1581,16 @@ describe("createTiptapComposerExtensions", () => {
|
|
|
1581
1581
|
availableModels: [
|
|
1582
1582
|
{
|
|
1583
1583
|
engine: "builder",
|
|
1584
|
-
label: "Builder.io
|
|
1584
|
+
label: "OpenAI · Builder.io",
|
|
1585
1585
|
models,
|
|
1586
1586
|
configured: true,
|
|
1587
1587
|
},
|
|
1588
|
+
{
|
|
1589
|
+
engine: "claude",
|
|
1590
|
+
label: "Claude · Builder.io",
|
|
1591
|
+
models: ["claude-haiku-4-5"],
|
|
1592
|
+
configured: true,
|
|
1593
|
+
},
|
|
1588
1594
|
],
|
|
1589
1595
|
selectedModel: "gpt-6-luna",
|
|
1590
1596
|
selectedEngine: "builder",
|
|
@@ -1619,6 +1625,9 @@ describe("createTiptapComposerExtensions", () => {
|
|
|
1619
1625
|
expect(picker?.textContent).toContain("GPT-6 Luna");
|
|
1620
1626
|
expect(picker?.textContent).toContain("Claude Opus 5.5");
|
|
1621
1627
|
expect(picker?.textContent).toContain("Gemini 3.8 Flash");
|
|
1628
|
+
expect(picker?.textContent).toContain("OpenAI");
|
|
1629
|
+
expect(picker?.textContent).toContain("Claude");
|
|
1630
|
+
expect(picker?.textContent).not.toContain("Builder.io");
|
|
1622
1631
|
});
|
|
1623
1632
|
|
|
1624
1633
|
it("chooses the newest tier version regardless of catalog order", () => {
|
|
@@ -1983,8 +1992,11 @@ describe("TiptapComposer slash commands", () => {
|
|
|
1983
1992
|
'[data-agent-composer-slot="send-button"]',
|
|
1984
1993
|
);
|
|
1985
1994
|
expect(sendButton?.disabled).toBe(true);
|
|
1986
|
-
expect(sendButton?.getAttribute("aria-label")).toMatch(/loading/i);
|
|
1995
|
+
expect(sendButton?.getAttribute("aria-label")).not.toMatch(/loading/i);
|
|
1987
1996
|
expect(sendButton?.getAttribute("aria-label")).not.toMatch(/checking/i);
|
|
1997
|
+
expect(sendButton?.getAttribute("aria-busy")).toBeNull();
|
|
1998
|
+
expect(sendButton?.querySelector(".animate-spin")).toBeNull();
|
|
1999
|
+
expect(sendButton?.querySelector(".tabler-icon-arrow-up")).not.toBeNull();
|
|
1988
2000
|
});
|
|
1989
2001
|
|
|
1990
2002
|
it("keeps the editor focused after a successful submission", async () => {
|
|
@@ -2166,7 +2178,13 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2166
2178
|
resolveSubmit = resolve;
|
|
2167
2179
|
});
|
|
2168
2180
|
const onBeforeSubmit = vi.fn(() => preflight);
|
|
2169
|
-
const
|
|
2181
|
+
const onSubmissionPendingChange = vi.fn();
|
|
2182
|
+
const onSubmit = vi.fn<NonNullable<TiptapComposerProps["onSubmit"]>>(
|
|
2183
|
+
async (_text, _references, _attachments, options) => {
|
|
2184
|
+
await submission;
|
|
2185
|
+
options?.onLocalSubmit?.();
|
|
2186
|
+
},
|
|
2187
|
+
);
|
|
2170
2188
|
const focusRef = React.createRef<TiptapComposerHandle>();
|
|
2171
2189
|
|
|
2172
2190
|
function Harness() {
|
|
@@ -2180,6 +2198,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2180
2198
|
React.createElement(TiptapComposer, {
|
|
2181
2199
|
focusRef,
|
|
2182
2200
|
onBeforeSubmit,
|
|
2201
|
+
onSubmissionPendingChange,
|
|
2183
2202
|
onSubmit,
|
|
2184
2203
|
clearOnSubmitImmediately: true,
|
|
2185
2204
|
includeDefaultSlashSkills: false,
|
|
@@ -2209,6 +2228,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2209
2228
|
|
|
2210
2229
|
expect(editor.textContent).toBe("");
|
|
2211
2230
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
2231
|
+
expect(onSubmissionPendingChange).toHaveBeenLastCalledWith(true);
|
|
2212
2232
|
act(() => focusRef.current?.setText("follow-up prompt"));
|
|
2213
2233
|
|
|
2214
2234
|
await act(async () => {
|
|
@@ -2225,6 +2245,8 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2225
2245
|
);
|
|
2226
2246
|
expect(editor.textContent).toBe("follow-up prompt");
|
|
2227
2247
|
await act(async () => resolveSubmit());
|
|
2248
|
+
expect(editor.textContent).toBe("follow-up prompt");
|
|
2249
|
+
expect(onSubmissionPendingChange).toHaveBeenLastCalledWith(false);
|
|
2228
2250
|
});
|
|
2229
2251
|
|
|
2230
2252
|
it("restores a prompt when async preflight declines it", async () => {
|
|
@@ -2337,10 +2359,11 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2337
2359
|
|
|
2338
2360
|
it("clears immediately while submitting and restores the draft on failure", async () => {
|
|
2339
2361
|
let rejectSubmit!: (error: Error) => void;
|
|
2340
|
-
const onSubmit = vi.fn(
|
|
2341
|
-
() =>
|
|
2362
|
+
const onSubmit = vi.fn<NonNullable<TiptapComposerProps["onSubmit"]>>(
|
|
2363
|
+
(_text, _references, _attachments, options) =>
|
|
2342
2364
|
new Promise<void>((_resolve, reject) => {
|
|
2343
2365
|
rejectSubmit = reject;
|
|
2366
|
+
options?.onLocalSubmit?.();
|
|
2344
2367
|
}),
|
|
2345
2368
|
);
|
|
2346
2369
|
const focusRef = React.createRef<TiptapComposerHandle>();
|
|
@@ -2384,6 +2407,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2384
2407
|
|
|
2385
2408
|
expect(onSubmit).toHaveBeenCalledOnce();
|
|
2386
2409
|
expect(editor.textContent).toBe("");
|
|
2410
|
+
act(() => focusRef.current?.setText("follow-up prompt"));
|
|
2387
2411
|
|
|
2388
2412
|
await act(async () => {
|
|
2389
2413
|
rejectSubmit(new Error("network unavailable"));
|
|
@@ -2391,7 +2415,17 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2391
2415
|
await Promise.resolve();
|
|
2392
2416
|
});
|
|
2393
2417
|
|
|
2394
|
-
expect(
|
|
2418
|
+
expect(
|
|
2419
|
+
[...editor.querySelectorAll("p")].map(
|
|
2420
|
+
(paragraph) => paragraph.textContent,
|
|
2421
|
+
),
|
|
2422
|
+
).toEqual(["send this once", "follow-up prompt"]);
|
|
2423
|
+
expect(localStorage.getItem(getComposerDraftKey())).toContain(
|
|
2424
|
+
"send this once",
|
|
2425
|
+
);
|
|
2426
|
+
expect(localStorage.getItem(getComposerDraftKey())).toContain(
|
|
2427
|
+
"follow-up prompt",
|
|
2428
|
+
);
|
|
2395
2429
|
});
|
|
2396
2430
|
|
|
2397
2431
|
it("restores the persisted draft when an immediate submit fails after unmount", async () => {
|
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
IconPencil,
|
|
15
15
|
IconPlugConnected,
|
|
16
16
|
IconHelpCircle,
|
|
17
|
-
IconRefresh,
|
|
18
17
|
IconAlertCircle,
|
|
19
18
|
IconLoader2,
|
|
19
|
+
IconRefresh,
|
|
20
20
|
} from "@tabler/icons-react";
|
|
21
21
|
import Placeholder from "@tiptap/extension-placeholder";
|
|
22
22
|
import { TextSelection } from "@tiptap/pm/state";
|
|
@@ -888,6 +888,8 @@ export interface TiptapComposerProps {
|
|
|
888
888
|
contextControlsDisabled?: boolean;
|
|
889
889
|
/** Prevent submission without making the editable surface lose focus. */
|
|
890
890
|
submissionDisabled?: boolean;
|
|
891
|
+
/** Disable only the send control while the submission is being accepted. */
|
|
892
|
+
sendButtonDisabled?: boolean;
|
|
891
893
|
/** Prevent submission while a host request is in flight. */
|
|
892
894
|
submitting?: boolean;
|
|
893
895
|
/** Override the generic document attachment cap for a multipart host. */
|
|
@@ -916,6 +918,7 @@ export interface TiptapComposerProps {
|
|
|
916
918
|
) => void | Promise<void>;
|
|
917
919
|
/** Return false to stop a submit before it enters the chat runtime. */
|
|
918
920
|
onBeforeSubmit?: () => boolean | Promise<boolean>;
|
|
921
|
+
onSubmissionPendingChange?: (pending: boolean) => void;
|
|
919
922
|
/** Scope where a failed submission should be recovered after the host forks. */
|
|
920
923
|
getSubmitFailureDraftScope?: () => string | null;
|
|
921
924
|
/**
|
|
@@ -2381,7 +2384,7 @@ function ModelSelector({
|
|
|
2381
2384
|
{showProviderLabels && (
|
|
2382
2385
|
<div className="group flex items-center px-2 py-1">
|
|
2383
2386
|
<span className="shrink-0 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
2384
|
-
{group.label}
|
|
2387
|
+
{group.label.replace(/ · Builder\.io$/i, "")}
|
|
2385
2388
|
</span>
|
|
2386
2389
|
{!group.configured && statusLabel && (
|
|
2387
2390
|
<button
|
|
@@ -2547,6 +2550,7 @@ export function TiptapComposer({
|
|
|
2547
2550
|
disabled = false,
|
|
2548
2551
|
contextControlsDisabled = false,
|
|
2549
2552
|
submissionDisabled = false,
|
|
2553
|
+
sendButtonDisabled = false,
|
|
2550
2554
|
submitting = false,
|
|
2551
2555
|
maxDocumentAttachmentBytes = MAX_DOCUMENT_ATTACHMENT_BYTES,
|
|
2552
2556
|
documentAttachmentLimitLabel = "PDFs",
|
|
@@ -2558,6 +2562,7 @@ export function TiptapComposer({
|
|
|
2558
2562
|
initialTextKey,
|
|
2559
2563
|
onSubmit,
|
|
2560
2564
|
onBeforeSubmit,
|
|
2565
|
+
onSubmissionPendingChange,
|
|
2561
2566
|
getSubmitFailureDraftScope,
|
|
2562
2567
|
clearOnSubmit = true,
|
|
2563
2568
|
clearOnSubmitImmediately = false,
|
|
@@ -2602,7 +2607,6 @@ export function TiptapComposer({
|
|
|
2602
2607
|
draftScope,
|
|
2603
2608
|
contextItems: providedContextItems,
|
|
2604
2609
|
onRemoveContextItem,
|
|
2605
|
-
onInspectContextItem,
|
|
2606
2610
|
onRetryContextItem,
|
|
2607
2611
|
contextMenuItems,
|
|
2608
2612
|
plusMenuMode = "full",
|
|
@@ -4243,7 +4247,6 @@ export function TiptapComposer({
|
|
|
4243
4247
|
slotReferencesRef.current.length > 0);
|
|
4244
4248
|
const canPreserveFollowUp =
|
|
4245
4249
|
preserveFollowUp &&
|
|
4246
|
-
clearedBeforePreflight &&
|
|
4247
4250
|
!isForkRecovery &&
|
|
4248
4251
|
hasFollowUp &&
|
|
4249
4252
|
(currentDraft === null ||
|
|
@@ -4330,6 +4333,7 @@ export function TiptapComposer({
|
|
|
4330
4333
|
|
|
4331
4334
|
if (onBeforeSubmit) {
|
|
4332
4335
|
submitInFlightRef.current = true;
|
|
4336
|
+
onSubmissionPendingChange?.(true);
|
|
4333
4337
|
try {
|
|
4334
4338
|
const shouldSubmit = await onBeforeSubmit();
|
|
4335
4339
|
if (!shouldSubmit) {
|
|
@@ -4351,6 +4355,7 @@ export function TiptapComposer({
|
|
|
4351
4355
|
return false;
|
|
4352
4356
|
} finally {
|
|
4353
4357
|
submitInFlightRef.current = false;
|
|
4358
|
+
onSubmissionPendingChange?.(false);
|
|
4354
4359
|
}
|
|
4355
4360
|
}
|
|
4356
4361
|
if (
|
|
@@ -4498,6 +4503,7 @@ export function TiptapComposer({
|
|
|
4498
4503
|
submitInFlightRef.current = true;
|
|
4499
4504
|
let locallySubmitted = false;
|
|
4500
4505
|
let settled = false;
|
|
4506
|
+
onSubmissionPendingChange?.(true);
|
|
4501
4507
|
const clearSubmittedComposer = () => {
|
|
4502
4508
|
if (clearOnSubmit) {
|
|
4503
4509
|
clearComposerDraft(submittingDraftKey, submittingDraftSnapshot);
|
|
@@ -4575,7 +4581,7 @@ export function TiptapComposer({
|
|
|
4575
4581
|
);
|
|
4576
4582
|
} catch (error) {
|
|
4577
4583
|
if (locallySubmitted) {
|
|
4578
|
-
restoreSubmittedDraft();
|
|
4584
|
+
restoreSubmittedDraft(true);
|
|
4579
4585
|
return true;
|
|
4580
4586
|
}
|
|
4581
4587
|
restoreSubmittedDraft(true);
|
|
@@ -4593,6 +4599,7 @@ export function TiptapComposer({
|
|
|
4593
4599
|
} finally {
|
|
4594
4600
|
settled = true;
|
|
4595
4601
|
submitInFlightRef.current = false;
|
|
4602
|
+
onSubmissionPendingChange?.(false);
|
|
4596
4603
|
}
|
|
4597
4604
|
|
|
4598
4605
|
if (!isCurrentDraftScope()) {
|
|
@@ -4685,6 +4692,7 @@ export function TiptapComposer({
|
|
|
4685
4692
|
clearOnSubmitImmediately,
|
|
4686
4693
|
getSubmitFailureDraftScope,
|
|
4687
4694
|
onBeforeSubmit,
|
|
4695
|
+
onSubmissionPendingChange,
|
|
4688
4696
|
extractComposerPayload,
|
|
4689
4697
|
syncComposerState,
|
|
4690
4698
|
updateSlotReferences,
|
|
@@ -5142,7 +5150,7 @@ export function TiptapComposer({
|
|
|
5142
5150
|
{slotReferences.map((ref) => (
|
|
5143
5151
|
<span
|
|
5144
5152
|
key={ref.slotKey}
|
|
5145
|
-
className="inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-background px-2 py-
|
|
5153
|
+
className="inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-background px-2 py-0.5 text-[11px] font-medium text-foreground shadow-sm"
|
|
5146
5154
|
>
|
|
5147
5155
|
<MentionItemMedia
|
|
5148
5156
|
media={ref.media}
|
|
@@ -5176,79 +5184,80 @@ export function TiptapComposer({
|
|
|
5176
5184
|
</span>
|
|
5177
5185
|
))}
|
|
5178
5186
|
{contextItems.map((item) => (
|
|
5179
|
-
<
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
{onInspectContextItem ? (
|
|
5211
|
-
<button
|
|
5212
|
-
type="button"
|
|
5213
|
-
onClick={() => onInspectContextItem(item.key)}
|
|
5214
|
-
className="min-w-0 truncate hover:underline"
|
|
5215
|
-
>
|
|
5216
|
-
{item.title}
|
|
5217
|
-
</button>
|
|
5218
|
-
) : (
|
|
5187
|
+
<React.Fragment key={item.key}>
|
|
5188
|
+
<span
|
|
5189
|
+
data-context-key={item.key}
|
|
5190
|
+
data-context-status={item.status ?? "ready"}
|
|
5191
|
+
title={item.statusMessage}
|
|
5192
|
+
data-state={
|
|
5193
|
+
selectedContextItemKey === item.key ? "selected" : undefined
|
|
5194
|
+
}
|
|
5195
|
+
className={`inline-flex max-w-full items-center gap-1.5 rounded-md border px-2 py-0.5 text-[11px] font-medium text-foreground ${
|
|
5196
|
+
selectedContextItemKey === item.key
|
|
5197
|
+
? "border-ring bg-accent ring-2 ring-ring/40"
|
|
5198
|
+
: "border-border bg-muted/50"
|
|
5199
|
+
}`}
|
|
5200
|
+
>
|
|
5201
|
+
{item.status === "pending" ? (
|
|
5202
|
+
<IconLoader2
|
|
5203
|
+
aria-label={t("agentChat.composer.contextPending", {
|
|
5204
|
+
defaultValue: "Context pending",
|
|
5205
|
+
})}
|
|
5206
|
+
className="size-3 shrink-0 animate-spin motion-reduce:animate-none text-muted-foreground"
|
|
5207
|
+
/>
|
|
5208
|
+
) : item.status === "error" ? (
|
|
5209
|
+
<IconAlertCircle
|
|
5210
|
+
aria-label={t("agentChat.composer.contextError", {
|
|
5211
|
+
defaultValue: "Context failed",
|
|
5212
|
+
})}
|
|
5213
|
+
className="size-3 shrink-0 text-destructive"
|
|
5214
|
+
/>
|
|
5215
|
+
) : (
|
|
5216
|
+
<IconClipboardList className="h-3 w-3 shrink-0 text-muted-foreground" />
|
|
5217
|
+
)}
|
|
5219
5218
|
<span className="min-w-0 truncate">{item.title}</span>
|
|
5220
|
-
|
|
5219
|
+
{onRemoveContextItem && item.removable !== false ? (
|
|
5220
|
+
<button
|
|
5221
|
+
type="button"
|
|
5222
|
+
onClick={() => {
|
|
5223
|
+
selectedContextItemKeyRef.current = null;
|
|
5224
|
+
setSelectedContextItemKey(null);
|
|
5225
|
+
onRemoveContextItem?.(item.key);
|
|
5226
|
+
}}
|
|
5227
|
+
aria-label={t("agentChat.composer.removeContext", {
|
|
5228
|
+
defaultValue: "Remove {{name}} context",
|
|
5229
|
+
name: item.title,
|
|
5230
|
+
})}
|
|
5231
|
+
className="ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
5232
|
+
>
|
|
5233
|
+
<IconX className="h-3 w-3" />
|
|
5234
|
+
</button>
|
|
5235
|
+
) : null}
|
|
5236
|
+
</span>
|
|
5221
5237
|
{item.status === "error" && onRetryContextItem ? (
|
|
5222
|
-
<
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
defaultValue: "Remove {{name}} context",
|
|
5244
|
-
name: item.title,
|
|
5245
|
-
})}
|
|
5246
|
-
className="ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
5247
|
-
>
|
|
5248
|
-
<IconX className="h-3 w-3" />
|
|
5249
|
-
</button>
|
|
5238
|
+
<Tooltip>
|
|
5239
|
+
<TooltipTrigger asChild>
|
|
5240
|
+
<button
|
|
5241
|
+
type="button"
|
|
5242
|
+
onClick={() => onRetryContextItem(item.key)}
|
|
5243
|
+
aria-label={t("agentChat.composer.retryContext", {
|
|
5244
|
+
defaultValue: "Retry {{name}} context",
|
|
5245
|
+
name: item.title,
|
|
5246
|
+
})}
|
|
5247
|
+
className="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
5248
|
+
>
|
|
5249
|
+
<IconRefresh aria-hidden="true" className="size-3.5" />
|
|
5250
|
+
</button>
|
|
5251
|
+
</TooltipTrigger>
|
|
5252
|
+
<TooltipContent>
|
|
5253
|
+
{t("agentChat.composer.retryContext", {
|
|
5254
|
+
defaultValue: "Retry {{name}} context",
|
|
5255
|
+
name: item.title,
|
|
5256
|
+
})}
|
|
5257
|
+
</TooltipContent>
|
|
5258
|
+
</Tooltip>
|
|
5250
5259
|
) : null}
|
|
5251
|
-
</
|
|
5260
|
+
</React.Fragment>
|
|
5252
5261
|
))}
|
|
5253
5262
|
</div>
|
|
5254
5263
|
)}
|
|
@@ -5391,24 +5400,15 @@ export function TiptapComposer({
|
|
|
5391
5400
|
<button
|
|
5392
5401
|
type="button"
|
|
5393
5402
|
onClick={() => void submitComposer("immediate")}
|
|
5394
|
-
disabled={!canSend}
|
|
5395
|
-
aria-label={
|
|
5396
|
-
submitting ? t("common.loading") : sendButtonTooltip
|
|
5397
|
-
}
|
|
5398
|
-
aria-busy={submitting || undefined}
|
|
5403
|
+
disabled={!canSend || sendButtonDisabled}
|
|
5404
|
+
aria-label={sendButtonTooltip}
|
|
5399
5405
|
data-agent-composer-slot="send-button"
|
|
5400
5406
|
className="agent-composer-send-button shrink-0 flex h-7 w-7 items-center justify-center rounded-full bg-primary text-primary-foreground hover:opacity-90 transition-[opacity,transform] duration-150 active:scale-[0.97] disabled:opacity-30 disabled:cursor-not-allowed"
|
|
5401
5407
|
>
|
|
5402
|
-
|
|
5403
|
-
<IconLoader2 className="size-3.5 animate-spin motion-reduce:animate-none" />
|
|
5404
|
-
) : (
|
|
5405
|
-
<IconArrowUp className="h-3.5 w-3.5" />
|
|
5406
|
-
)}
|
|
5408
|
+
<IconArrowUp className="h-3.5 w-3.5" />
|
|
5407
5409
|
</button>
|
|
5408
5410
|
</TooltipTrigger>
|
|
5409
|
-
<TooltipContent>
|
|
5410
|
-
{submitting ? t("common.loading") : sendButtonTooltip}
|
|
5411
|
-
</TooltipContent>
|
|
5411
|
+
<TooltipContent>{sendButtonTooltip}</TooltipContent>
|
|
5412
5412
|
</Tooltip>
|
|
5413
5413
|
)}
|
|
5414
5414
|
</>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
const source = readFileSync(new URL("./sonner.tsx", import.meta.url), "utf8");
|
|
6
|
+
|
|
7
|
+
describe("Sonner toast layout", () => {
|
|
8
|
+
it("allows toast content to shrink beside an action", () => {
|
|
9
|
+
expect(source).toContain("group-[.toast]:!min-w-0");
|
|
10
|
+
expect(source).not.toContain(
|
|
11
|
+
"group-[.toast]:!min-w-[min(16rem,calc(100vw_-_14rem))]",
|
|
12
|
+
);
|
|
13
|
+
expect(source).toContain("group-[.toast]:!shrink-0");
|
|
14
|
+
});
|
|
15
|
+
});
|
package/src/ui/sonner.tsx
CHANGED
|
@@ -8,7 +8,7 @@ type ToasterProps = React.ComponentProps<typeof Sonner>;
|
|
|
8
8
|
const toastWidthClasses =
|
|
9
9
|
"group-[.toaster]:!w-[var(--width)] group-[.toaster]:!min-w-[min(20rem,calc(100vw_-_2rem))] group-[.toaster]:!max-w-[var(--width)] group-[.toaster]:!gap-3 group-[.toaster]:!break-normal";
|
|
10
10
|
const toastContentClasses =
|
|
11
|
-
"group-[.toast]:!min-w-
|
|
11
|
+
"group-[.toast]:!min-w-0 group-[.toast]:!flex-1 group-[.toast]:!basis-auto group-[.toast]:break-words";
|
|
12
12
|
const toastButtonClasses =
|
|
13
13
|
"group-[.toast]:!shrink-0 group-[.toast]:!whitespace-nowrap";
|
|
14
14
|
|