@agent-native/toolkit 0.214.0 → 0.216.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/app/agentkit/react/chat.d.ts.map +1 -1
- package/dist/app/agentkit/react/chat.js +2 -2
- package/dist/app/agentkit/react/chat.js.map +1 -1
- package/dist/app/agentkit/react/components.d.ts +4 -1
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +69 -15
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/context.d.ts +7 -1
- package/dist/app/agentkit/react/context.d.ts.map +1 -1
- package/dist/app/agentkit/react/context.js +5 -1
- package/dist/app/agentkit/react/context.js.map +1 -1
- package/dist/app/agentkit/react/index.d.ts +1 -0
- package/dist/app/agentkit/react/index.d.ts.map +1 -1
- package/dist/app/agentkit/react/index.js +1 -0
- package/dist/app/agentkit/react/index.js.map +1 -1
- package/dist/app/agentkit/react/root.d.ts +4 -1
- package/dist/app/agentkit/react/root.d.ts.map +1 -1
- package/dist/app/agentkit/react/root.js +2 -2
- package/dist/app/agentkit/react/root.js.map +1 -1
- package/dist/app/agentkit/react/styles.css +43 -6
- package/dist/app/agentkit/react/transcript-layout.d.ts +51 -0
- package/dist/app/agentkit/react/transcript-layout.d.ts.map +1 -0
- package/dist/app/agentkit/react/transcript-layout.js +34 -0
- package/dist/app/agentkit/react/transcript-layout.js.map +1 -0
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +3 -3
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/AgentPanel.d.ts +2 -0
- package/dist/app/chat/AgentPanel.d.ts.map +1 -1
- package/dist/app/chat/AgentPanel.js +6 -2
- package/dist/app/chat/AgentPanel.js.map +1 -1
- package/dist/app/chat/chat/index.d.ts +2 -0
- package/dist/app/chat/chat/index.d.ts.map +1 -1
- package/dist/app/chat/chat/index.js.map +1 -1
- package/dist/app/chat/chat/surface-types.d.ts +7 -0
- package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
- package/dist/app/chat/chat/surface-types.js.map +1 -1
- package/dist/app/chat/chat/tool-call-display.d.ts +7 -2
- package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
- package/dist/app/chat/chat/tool-call-display.js +10 -9
- package/dist/app/chat/chat/tool-call-display.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartRenderer.js +196 -26
- package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartWidget.js +5 -1
- package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +2 -2
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +6 -4
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.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/ComposerContextBanner.d.ts +3 -1
- package/dist/composer/ComposerContextBanner.d.ts.map +1 -1
- package/dist/composer/ComposerContextBanner.js +2 -2
- package/dist/composer/ComposerContextBanner.js.map +1 -1
- package/package.json +3 -3
- package/src/app/agentkit/react/chat.tsx +2 -0
- package/src/app/agentkit/react/components.interaction.spec.tsx +107 -0
- package/src/app/agentkit/react/components.spec.tsx +28 -1
- package/src/app/agentkit/react/components.tsx +153 -17
- package/src/app/agentkit/react/context.tsx +12 -0
- package/src/app/agentkit/react/index.ts +1 -0
- package/src/app/agentkit/react/lifecycle.spec.tsx +186 -16
- package/src/app/agentkit/react/root.tsx +7 -0
- package/src/app/agentkit/react/styles.css +43 -6
- package/src/app/agentkit/react/transcript-layout.spec.ts +72 -0
- package/src/app/agentkit/react/transcript-layout.ts +97 -0
- package/src/app/auth/AuthPage.attribution.spec.tsx +6 -0
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +39 -0
- package/src/app/chat/AgentKitAssistantChat.tsx +5 -0
- package/src/app/chat/AgentPanel.page-header-share.spec.tsx +101 -0
- package/src/app/chat/AgentPanel.tsx +14 -2
- package/src/app/chat/chat/index.ts +2 -0
- package/src/app/chat/chat/surface-types.ts +10 -0
- package/src/app/chat/chat/tool-call-display.spec.tsx +83 -0
- package/src/app/chat/chat/tool-call-display.tsx +40 -8
- package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +632 -0
- package/src/app/chat/chat/widgets/DataChartRenderer.tsx +308 -51
- package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +126 -23
- package/src/app/chat/chat/widgets/DataChartWidget.tsx +10 -0
- package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +26 -6
- package/src/app/i18n/catalogs/ar-SA.ts +1 -0
- package/src/app/i18n/catalogs/de-DE.ts +2 -0
- package/src/app/i18n/catalogs/en-US.ts +2 -0
- package/src/app/i18n/catalogs/es-ES.ts +2 -0
- package/src/app/i18n/catalogs/fr-FR.ts +2 -0
- package/src/app/i18n/catalogs/hi-IN.ts +1 -0
- package/src/app/i18n/catalogs/ja-JP.ts +2 -0
- package/src/app/i18n/catalogs/ko-KR.ts +1 -0
- package/src/app/i18n/catalogs/pt-BR.ts +2 -0
- package/src/app/i18n/catalogs/zh-CN.ts +1 -0
- package/src/app/i18n/catalogs/zh-TW.ts +1 -0
- package/src/composer/ComposerContextBanner.spec.tsx +31 -5
- package/src/composer/ComposerContextBanner.tsx +7 -2
|
@@ -669,11 +669,18 @@ body:has(> [data-agent-native-composer-popover="true"]) > .fixed.inset-0 {
|
|
|
669
669
|
}
|
|
670
670
|
|
|
671
671
|
.agentkit-transcript-content {
|
|
672
|
+
position: relative;
|
|
672
673
|
box-sizing: border-box;
|
|
673
|
-
inline-size: min(100%, 47rem);
|
|
674
|
+
inline-size: min(100%, var(--agentkit-transcript-lane-width, 47rem));
|
|
674
675
|
min-inline-size: 0;
|
|
675
676
|
margin-inline: auto;
|
|
677
|
+
inset-inline-start: clamp(
|
|
678
|
+
calc((100% - min(100%, var(--agentkit-transcript-lane-width, 47rem))) / -2),
|
|
679
|
+
var(--agentkit-transcript-lane-offset-inline, 0px),
|
|
680
|
+
calc((100% - min(100%, var(--agentkit-transcript-lane-width, 47rem))) / 2)
|
|
681
|
+
);
|
|
676
682
|
padding: 1.5rem 1rem;
|
|
683
|
+
padding-block-start: var(--agentkit-transcript-top-inset, 1.5rem);
|
|
677
684
|
}
|
|
678
685
|
|
|
679
686
|
.agentkit-scroll-to-bottom-control {
|
|
@@ -710,13 +717,21 @@ body:has(> [data-agent-native-composer-popover="true"]) > .fixed.inset-0 {
|
|
|
710
717
|
display: flex;
|
|
711
718
|
flex-direction: column;
|
|
712
719
|
align-items: flex-start;
|
|
713
|
-
margin-block: 0 1.25rem;
|
|
720
|
+
margin-block: 0 var(--agentkit-transcript-message-gap, 1.25rem);
|
|
714
721
|
font-size: 0.875rem;
|
|
715
|
-
|
|
722
|
+
font-family: var(
|
|
723
|
+
--agentkit-transcript-font-family,
|
|
724
|
+
var(--font-sans, inherit)
|
|
725
|
+
);
|
|
726
|
+
line-height: var(--agentkit-transcript-message-line-height, 1.52);
|
|
716
727
|
}
|
|
717
728
|
|
|
718
729
|
.agentkit-message[data-role="user"] {
|
|
719
730
|
align-items: flex-end;
|
|
731
|
+
line-height: var(
|
|
732
|
+
--agentkit-user-bubble-line-height,
|
|
733
|
+
var(--agentkit-transcript-message-line-height, 1.52)
|
|
734
|
+
);
|
|
720
735
|
}
|
|
721
736
|
|
|
722
737
|
.agentkit-message-content {
|
|
@@ -726,8 +741,11 @@ body:has(> [data-agent-native-composer-popover="true"]) > .fixed.inset-0 {
|
|
|
726
741
|
}
|
|
727
742
|
|
|
728
743
|
.agentkit-message[data-role="user"] .agentkit-message-content {
|
|
729
|
-
|
|
730
|
-
|
|
744
|
+
position: relative;
|
|
745
|
+
inset-inline-start: var(--agentkit-user-bubble-offset-inline, 0px);
|
|
746
|
+
max-inline-size: min(88%, var(--agentkit-user-bubble-max-width, 38rem));
|
|
747
|
+
padding: var(--agentkit-user-bubble-padding-block, 0.625rem)
|
|
748
|
+
var(--agentkit-user-bubble-padding-inline, 0.8125rem);
|
|
731
749
|
border-radius: var(--agentkit-radius-inner);
|
|
732
750
|
background: var(--agentkit-subtle);
|
|
733
751
|
}
|
|
@@ -845,6 +863,25 @@ body:has(> [data-agent-native-composer-popover="true"]) > .fixed.inset-0 {
|
|
|
845
863
|
background: var(--agentkit-canvas);
|
|
846
864
|
}
|
|
847
865
|
|
|
866
|
+
.agentkit-message-content [data-post-widget-text="true"] {
|
|
867
|
+
display: contents;
|
|
868
|
+
color: var(--agentkit-transcript-post-widget-text-color, inherit);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.agentkit-message-content [data-post-widget-text="true"] > p,
|
|
872
|
+
.agentkit-message-content [data-post-widget-text="true"] .agentkit-markdown p {
|
|
873
|
+
padding-block: var(
|
|
874
|
+
--agentkit-transcript-post-widget-paragraph-padding-block,
|
|
875
|
+
0px
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.agentkit-message-content
|
|
880
|
+
> [data-post-widget-text="true"]:last-child
|
|
881
|
+
> :last-child {
|
|
882
|
+
margin-block-end: 0;
|
|
883
|
+
}
|
|
884
|
+
|
|
848
885
|
.agentkit-image-preview img {
|
|
849
886
|
max-inline-size: 92vw;
|
|
850
887
|
max-block-size: 90vh;
|
|
@@ -2133,6 +2170,6 @@ button.agentkit-object {
|
|
|
2133
2170
|
}
|
|
2134
2171
|
|
|
2135
2172
|
.agentkit-message[data-role="user"] .agentkit-message-content {
|
|
2136
|
-
max-inline-size: 94
|
|
2173
|
+
max-inline-size: min(94%, var(--agentkit-user-bubble-max-width, 38rem));
|
|
2137
2174
|
}
|
|
2138
2175
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AgentKitController } from "@agent-native/agentkit/client";
|
|
2
|
+
import { createElement } from "react";
|
|
3
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { useAgentKit } from "./context.js";
|
|
7
|
+
import { AgentKitRoot } from "./root.js";
|
|
8
|
+
import {
|
|
9
|
+
workedForRowChevronStyle,
|
|
10
|
+
workedForRowSummaryStyle,
|
|
11
|
+
} from "./transcript-layout.js";
|
|
12
|
+
|
|
13
|
+
function WorkedForLayoutProbe() {
|
|
14
|
+
const { workedForRowLayout, formatWorkedForDuration } = useAgentKit();
|
|
15
|
+
return createElement("span", {
|
|
16
|
+
"data-icon-gap": workedForRowLayout?.iconGap,
|
|
17
|
+
"data-duration": formatWorkedForDuration?.(16_000),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("worked-for row layout", () => {
|
|
22
|
+
it("applies shared dimensions to the summary and leading chevron", () => {
|
|
23
|
+
const layout = {
|
|
24
|
+
chevronSize: 12,
|
|
25
|
+
iconGap: 8,
|
|
26
|
+
fontSize: 13,
|
|
27
|
+
lineHeight: 16,
|
|
28
|
+
paddingBlock: 2,
|
|
29
|
+
paddingInline: 4,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
expect(workedForRowSummaryStyle(layout)).toEqual({
|
|
33
|
+
gap: "8px",
|
|
34
|
+
fontSize: "13px",
|
|
35
|
+
lineHeight: "16px",
|
|
36
|
+
paddingBlock: "2px",
|
|
37
|
+
paddingInline: "4px",
|
|
38
|
+
fontFamily: "inherit",
|
|
39
|
+
});
|
|
40
|
+
expect(workedForRowChevronStyle(layout)).toEqual({
|
|
41
|
+
width: "12px",
|
|
42
|
+
height: "12px",
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("rejects invalid CSS dimensions", () => {
|
|
47
|
+
expect(() => workedForRowSummaryStyle({ fontSize: 0 })).toThrow(
|
|
48
|
+
"workedForRowLayout.fontSize must be a valid CSS length",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("carries the layout from the AgentKit root into shared context", () => {
|
|
53
|
+
const layout = { iconGap: 8 };
|
|
54
|
+
const formatWorkedForDuration = (durationMs: number) =>
|
|
55
|
+
`${Math.round(durationMs / 1000)} seconds`;
|
|
56
|
+
const markup = renderToStaticMarkup(
|
|
57
|
+
createElement(
|
|
58
|
+
AgentKitRoot,
|
|
59
|
+
{
|
|
60
|
+
controller: {} as AgentKitController,
|
|
61
|
+
threadId: "thread",
|
|
62
|
+
workedForRowLayout: layout,
|
|
63
|
+
formatWorkedForDuration,
|
|
64
|
+
},
|
|
65
|
+
createElement(WorkedForLayoutProbe),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(markup).toContain('data-icon-gap="8"');
|
|
70
|
+
expect(markup).toContain('data-duration="16 seconds"');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
export interface AgentChatTranscriptLayout {
|
|
4
|
+
/** Top padding above transcript messages, in CSS pixels. */
|
|
5
|
+
topInset?: number;
|
|
6
|
+
/** Maximum centered message lane width, in CSS pixels. */
|
|
7
|
+
messageLaneWidth?: number;
|
|
8
|
+
/** Signed inline offset for the centered message lane, in CSS pixels. */
|
|
9
|
+
messageLaneOffsetInline?: number;
|
|
10
|
+
/** Space after each message, in CSS pixels. */
|
|
11
|
+
messageGap?: number;
|
|
12
|
+
/** Assistant message line height, in CSS pixels. */
|
|
13
|
+
messageLineHeight?: number;
|
|
14
|
+
/** Font family for transcript messages. Omitted uses the shared semantic font. */
|
|
15
|
+
fontFamily?: string;
|
|
16
|
+
/** Optional appearance for assistant text displayed after a widget. */
|
|
17
|
+
postWidgetText?: {
|
|
18
|
+
tone?: "muted";
|
|
19
|
+
paragraphPaddingBlock?: number;
|
|
20
|
+
};
|
|
21
|
+
/** User bubble line height, in CSS pixels. */
|
|
22
|
+
userBubbleLineHeight?: number;
|
|
23
|
+
/** Maximum user bubble width, in CSS pixels. */
|
|
24
|
+
userBubbleMaxWidth?: number;
|
|
25
|
+
/** Signed inline offset for the user bubble, in CSS pixels. */
|
|
26
|
+
userBubbleOffsetInline?: number;
|
|
27
|
+
/** User bubble padding on the block and inline axes, in CSS pixels. */
|
|
28
|
+
userBubblePadding?: { block?: number; inline?: number };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Optional geometry for the completed-work disclosure row in chat. */
|
|
32
|
+
export interface AgentChatWorkedForRowLayout {
|
|
33
|
+
/** Chevron width and height, in CSS pixels. */
|
|
34
|
+
chevronSize?: number;
|
|
35
|
+
/** Space between the chevron and label, in CSS pixels. */
|
|
36
|
+
iconGap?: number;
|
|
37
|
+
/** Signed margin before the row, in CSS pixels. */
|
|
38
|
+
marginBlockStart?: number;
|
|
39
|
+
/** Label font size, in CSS pixels. */
|
|
40
|
+
fontSize?: number;
|
|
41
|
+
/** Label line height, in CSS pixels. */
|
|
42
|
+
lineHeight?: number;
|
|
43
|
+
/** Padding on the block axis, in CSS pixels. */
|
|
44
|
+
paddingBlock?: number;
|
|
45
|
+
/** Padding on the inline axis, in CSS pixels. */
|
|
46
|
+
paddingInline?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function workedForRowLength(
|
|
50
|
+
value: number | undefined,
|
|
51
|
+
name: keyof AgentChatWorkedForRowLayout,
|
|
52
|
+
positive = false,
|
|
53
|
+
) {
|
|
54
|
+
if (value === undefined) return undefined;
|
|
55
|
+
if (!Number.isFinite(value) || value < 0 || (positive && value === 0)) {
|
|
56
|
+
throw new RangeError(
|
|
57
|
+
`workedForRowLayout.${name} must be a valid CSS length`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return `${value}px`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function workedForRowOffset(
|
|
64
|
+
value: number | undefined,
|
|
65
|
+
name: keyof AgentChatWorkedForRowLayout,
|
|
66
|
+
) {
|
|
67
|
+
if (value === undefined) return undefined;
|
|
68
|
+
if (!Number.isFinite(value)) {
|
|
69
|
+
throw new RangeError(`workedForRowLayout.${name} must be finite`);
|
|
70
|
+
}
|
|
71
|
+
return `${value}px`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function workedForRowSummaryStyle(
|
|
75
|
+
layout?: AgentChatWorkedForRowLayout,
|
|
76
|
+
): CSSProperties | undefined {
|
|
77
|
+
if (!layout) return undefined;
|
|
78
|
+
return {
|
|
79
|
+
gap: workedForRowLength(layout.iconGap, "iconGap"),
|
|
80
|
+
marginBlockStart: workedForRowOffset(
|
|
81
|
+
layout.marginBlockStart,
|
|
82
|
+
"marginBlockStart",
|
|
83
|
+
),
|
|
84
|
+
fontSize: workedForRowLength(layout.fontSize, "fontSize", true),
|
|
85
|
+
lineHeight: workedForRowLength(layout.lineHeight, "lineHeight", true),
|
|
86
|
+
paddingBlock: workedForRowLength(layout.paddingBlock, "paddingBlock"),
|
|
87
|
+
paddingInline: workedForRowLength(layout.paddingInline, "paddingInline"),
|
|
88
|
+
fontFamily: "inherit",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function workedForRowChevronStyle(
|
|
93
|
+
layout?: AgentChatWorkedForRowLayout,
|
|
94
|
+
): CSSProperties | undefined {
|
|
95
|
+
const size = workedForRowLength(layout?.chevronSize, "chevronSize", true);
|
|
96
|
+
return size ? { width: size, height: size } : undefined;
|
|
97
|
+
}
|
|
@@ -129,6 +129,12 @@ async function recordBoth(
|
|
|
129
129
|
|
|
130
130
|
describe("AuthPage attribution", () => {
|
|
131
131
|
beforeEach(() => {
|
|
132
|
+
// Attribution storage depends on the visitor's time zone, so pin it.
|
|
133
|
+
const realOptions = new Intl.DateTimeFormat().resolvedOptions();
|
|
134
|
+
vi.spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions").mockReturnValue({
|
|
135
|
+
...realOptions,
|
|
136
|
+
timeZone: "America/Los_Angeles",
|
|
137
|
+
});
|
|
132
138
|
vi.useFakeTimers({ toFake: ["Date"] });
|
|
133
139
|
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
134
140
|
vi.stubGlobal(
|
|
@@ -1054,6 +1054,27 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
1054
1054
|
expect(chatMocks.composerProps.isReferenceTarget).toBe(true);
|
|
1055
1055
|
});
|
|
1056
1056
|
|
|
1057
|
+
it("passes the worked-for row layout into the shared AgentKit root", async () => {
|
|
1058
|
+
const layout = {
|
|
1059
|
+
chevronSize: 12,
|
|
1060
|
+
iconGap: 8,
|
|
1061
|
+
fontSize: 13,
|
|
1062
|
+
lineHeight: 16,
|
|
1063
|
+
paddingBlock: 2,
|
|
1064
|
+
paddingInline: 4,
|
|
1065
|
+
};
|
|
1066
|
+
const formatWorkedForDuration = (durationMs: number) =>
|
|
1067
|
+
`${Math.round(durationMs / 1000)} seconds`;
|
|
1068
|
+
await mount(
|
|
1069
|
+
baseProps({ workedForRowLayout: layout, formatWorkedForDuration }),
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1072
|
+
expect(chatMocks.rootProps.workedForRowLayout).toEqual(layout);
|
|
1073
|
+
expect(chatMocks.rootProps.formatWorkedForDuration).toBe(
|
|
1074
|
+
formatWorkedForDuration,
|
|
1075
|
+
);
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1057
1078
|
it("reports whether a composer can hold a prefill with its current context", async () => {
|
|
1058
1079
|
const ref = createRef<AssistantChatHandle>();
|
|
1059
1080
|
await mount(baseProps(), ref);
|
|
@@ -3271,6 +3292,24 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
3271
3292
|
).not.toBeNull();
|
|
3272
3293
|
});
|
|
3273
3294
|
|
|
3295
|
+
it("forwards transcript layout options to the shared chat component", async () => {
|
|
3296
|
+
const transcriptLayout = {
|
|
3297
|
+
topInset: 5,
|
|
3298
|
+
messageLaneWidth: 730,
|
|
3299
|
+
messageLaneOffsetInline: -33,
|
|
3300
|
+
messageGap: 12,
|
|
3301
|
+
messageLineHeight: 20,
|
|
3302
|
+
fontFamily: "IBM Plex Sans",
|
|
3303
|
+
userBubbleLineHeight: 18,
|
|
3304
|
+
userBubbleMaxWidth: 600,
|
|
3305
|
+
userBubbleOffsetInline: 10,
|
|
3306
|
+
userBubblePadding: { block: 8, inline: 13 },
|
|
3307
|
+
};
|
|
3308
|
+
await mount(baseProps({ transcriptLayout }));
|
|
3309
|
+
|
|
3310
|
+
expect(chatMocks.chatProps.transcriptLayout).toBe(transcriptLayout);
|
|
3311
|
+
});
|
|
3312
|
+
|
|
3274
3313
|
it("anchors the composer at the bottom while restoring a persisted thread", async () => {
|
|
3275
3314
|
await mount(
|
|
3276
3315
|
baseProps({
|
|
@@ -2107,6 +2107,8 @@ export const AgentKitAssistantChat = forwardRef<
|
|
|
2107
2107
|
branchNavigation={props.branchNavigation}
|
|
2108
2108
|
loadRunUsage={loadRunUsage}
|
|
2109
2109
|
buildFeedbackReport={formatFeedbackReport}
|
|
2110
|
+
workedForRowLayout={props.workedForRowLayout}
|
|
2111
|
+
formatWorkedForDuration={props.formatWorkedForDuration}
|
|
2110
2112
|
onThreadForked={(thread) => props.onForkedThread?.(thread.id)}
|
|
2111
2113
|
onCopyMessage={({ text }) => {
|
|
2112
2114
|
const html = renderMarkdownToClipboardHtml(text);
|
|
@@ -5054,6 +5056,7 @@ const AgentKitAssistantChatBody = forwardRef<
|
|
|
5054
5056
|
className={props.className}
|
|
5055
5057
|
composerProps={{ attachmentsEnabled: fileStorageConfigured }}
|
|
5056
5058
|
hasRenderedMessages={hasRenderedMessages}
|
|
5059
|
+
transcriptLayout={props.transcriptLayout}
|
|
5057
5060
|
emptyComposerPlacement={
|
|
5058
5061
|
props.centerComposerWhenEmpty && !isRestoring ? "center" : "bottom"
|
|
5059
5062
|
}
|
|
@@ -6470,6 +6473,8 @@ function AgentKitTool({ value, active }: AgentKitRenderProps<AgentToolCall>) {
|
|
|
6470
6473
|
chatUI={asRecord(metadata.chatUI) as never}
|
|
6471
6474
|
activity={metadata.activity === true}
|
|
6472
6475
|
isActiveTail={active === true}
|
|
6476
|
+
workedForRowLayout={surface.props.workedForRowLayout}
|
|
6477
|
+
formatWorkedForDuration={surface.props.formatWorkedForDuration}
|
|
6473
6478
|
/>
|
|
6474
6479
|
</SuppressInlineOpenAppContext.Provider>
|
|
6475
6480
|
</ChatRunningContext.Provider>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act } from "react";
|
|
4
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
+
import { MemoryRouter } from "react-router";
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { AgentPanel } from "./AgentPanel.js";
|
|
9
|
+
|
|
10
|
+
type MockHeaderProps = {
|
|
11
|
+
renderOverlay?: (props: never) => React.ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
vi.mock("./MultiTabAssistantChat.js", () => ({
|
|
15
|
+
MultiTabAssistantChat: ({ renderOverlay }: MockHeaderProps) =>
|
|
16
|
+
renderOverlay?.({
|
|
17
|
+
tabs: [{ id: "thread-1", label: "Current thread", status: "completed" }],
|
|
18
|
+
activeTabId: "thread-1",
|
|
19
|
+
activeTabMessageCount: 1,
|
|
20
|
+
activeTabIsPersisted: true,
|
|
21
|
+
setActiveTabId: vi.fn(),
|
|
22
|
+
addTab: vi.fn(),
|
|
23
|
+
closeTab: vi.fn(),
|
|
24
|
+
closeOtherTabs: vi.fn(),
|
|
25
|
+
closeAllTabs: vi.fn(),
|
|
26
|
+
clearActiveTab: vi.fn(),
|
|
27
|
+
showHistory: false,
|
|
28
|
+
toggleHistory: vi.fn(),
|
|
29
|
+
tabCount: 1,
|
|
30
|
+
} as never) ?? null,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock("../sharing/index.js", () => ({
|
|
34
|
+
ShareButton: ({
|
|
35
|
+
triggerContent,
|
|
36
|
+
triggerClassName,
|
|
37
|
+
}: {
|
|
38
|
+
triggerContent?: React.ReactNode;
|
|
39
|
+
triggerClassName?: string;
|
|
40
|
+
}) => (
|
|
41
|
+
<button data-testid="share-trigger" data-trigger-class={triggerClassName}>
|
|
42
|
+
{triggerContent ?? "Share"}
|
|
43
|
+
</button>
|
|
44
|
+
),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
describe("AgentPanel page header share trigger", () => {
|
|
48
|
+
let root: Root | null = null;
|
|
49
|
+
let container: HTMLDivElement | null = null;
|
|
50
|
+
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
if (root) await act(async () => root?.unmount());
|
|
53
|
+
container?.remove();
|
|
54
|
+
root = null;
|
|
55
|
+
container = null;
|
|
56
|
+
localStorage.clear();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("renders the default icon and host-provided content through ShareButton", async () => {
|
|
60
|
+
container = document.createElement("div");
|
|
61
|
+
document.body.append(container);
|
|
62
|
+
root = createRoot(container);
|
|
63
|
+
|
|
64
|
+
const renderPanel = async (triggerContent?: React.ReactNode) =>
|
|
65
|
+
act(async () => {
|
|
66
|
+
root!.render(
|
|
67
|
+
<MemoryRouter>
|
|
68
|
+
<AgentPanel
|
|
69
|
+
storageKey="page-header-share-test"
|
|
70
|
+
showHeader={false}
|
|
71
|
+
showPageHeader
|
|
72
|
+
pageHeaderShareTriggerContent={triggerContent}
|
|
73
|
+
/>
|
|
74
|
+
</MemoryRouter>,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await renderPanel();
|
|
79
|
+
const defaultButton = container.querySelector<HTMLButtonElement>(
|
|
80
|
+
'[data-testid="share-trigger"]',
|
|
81
|
+
);
|
|
82
|
+
expect(defaultButton?.querySelector("svg")).not.toBeNull();
|
|
83
|
+
expect(defaultButton?.dataset.triggerClass).toContain("size-8");
|
|
84
|
+
|
|
85
|
+
await renderPanel(
|
|
86
|
+
<span className="flex items-center gap-1.5">
|
|
87
|
+
<svg data-testid="custom-share-icon" aria-hidden="true" />
|
|
88
|
+
<span>Share</span>
|
|
89
|
+
</span>,
|
|
90
|
+
);
|
|
91
|
+
const customButton = container.querySelector<HTMLButtonElement>(
|
|
92
|
+
'[data-testid="share-trigger"]',
|
|
93
|
+
);
|
|
94
|
+
expect(customButton?.textContent).toBe("Share");
|
|
95
|
+
expect(
|
|
96
|
+
customButton?.querySelector('[data-testid="custom-share-icon"]'),
|
|
97
|
+
).not.toBeNull();
|
|
98
|
+
expect(customButton?.dataset.triggerClass).toContain("w-auto");
|
|
99
|
+
expect(customButton?.dataset.triggerClass).toContain("px-2");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -610,6 +610,8 @@ export interface AgentPanelProps extends Omit<
|
|
|
610
610
|
/** Hide the default page title menu while keeping the shared page actions. */
|
|
611
611
|
showPageTitleMenu?: boolean;
|
|
612
612
|
pageHeaderLeadingSlot?: React.ReactNode;
|
|
613
|
+
/** Override the shared page header share button's icon and label contents. */
|
|
614
|
+
pageHeaderShareTriggerContent?: React.ReactNode;
|
|
613
615
|
/** Render host page actions alongside Share, with the active chat controls. */
|
|
614
616
|
pageToolbarSlot?:
|
|
615
617
|
| React.ReactNode
|
|
@@ -809,6 +811,7 @@ function AgentPanelInner({
|
|
|
809
811
|
showPageHeaderWhenEmpty = false,
|
|
810
812
|
showPageTitleMenu = true,
|
|
811
813
|
pageHeaderLeadingSlot,
|
|
814
|
+
pageHeaderShareTriggerContent,
|
|
812
815
|
pageToolbarSlot,
|
|
813
816
|
onPageHeaderVisibilityChange,
|
|
814
817
|
chatOnly = false,
|
|
@@ -1836,9 +1839,17 @@ function AgentPanelInner({
|
|
|
1836
1839
|
resourceTitle={activeTab.label || t("agentPanel.chat")}
|
|
1837
1840
|
shareUrl={getChatThreadShareUrl(activeTab.id)}
|
|
1838
1841
|
triggerContent={
|
|
1839
|
-
|
|
1842
|
+
pageHeaderShareTriggerContent ?? (
|
|
1843
|
+
<IconShare3 size={15} aria-hidden="true" />
|
|
1844
|
+
)
|
|
1840
1845
|
}
|
|
1841
|
-
triggerClassName=
|
|
1846
|
+
triggerClassName={cn(
|
|
1847
|
+
"h-8 border-0 bg-transparent shadow-none hover:bg-accent/60",
|
|
1848
|
+
pageHeaderShareTriggerContent === undefined ||
|
|
1849
|
+
pageHeaderShareTriggerContent === null
|
|
1850
|
+
? "size-8 p-0"
|
|
1851
|
+
: "w-auto px-2",
|
|
1852
|
+
)}
|
|
1842
1853
|
/>
|
|
1843
1854
|
) : null}
|
|
1844
1855
|
{toolbarSlot}
|
|
@@ -1869,6 +1880,7 @@ function AgentPanelInner({
|
|
|
1869
1880
|
headerMenuOpen,
|
|
1870
1881
|
onPageHeaderVisibilityChange,
|
|
1871
1882
|
pageHeaderLeadingSlot,
|
|
1883
|
+
pageHeaderShareTriggerContent,
|
|
1872
1884
|
pageToolbarSlot,
|
|
1873
1885
|
pendingHeaderOverlayRef,
|
|
1874
1886
|
setHeaderMenuOpen,
|
|
@@ -53,6 +53,8 @@ export type {
|
|
|
53
53
|
AssistantChatSubmitResult,
|
|
54
54
|
AgentChatSurfaceKind,
|
|
55
55
|
} from "./surface-types.js";
|
|
56
|
+
export type { AgentChatTranscriptLayout } from "../../agentkit/react/transcript-layout.js";
|
|
57
|
+
export type { AgentChatWorkedForRowLayout } from "../../agentkit/react/transcript-layout.js";
|
|
56
58
|
export type {
|
|
57
59
|
MultiTabAssistantChatProps,
|
|
58
60
|
MultiTabAssistantChatHeaderProps,
|
|
@@ -28,6 +28,10 @@ import type * as React from "react";
|
|
|
28
28
|
|
|
29
29
|
import type { AgentKitWorkedForSummaryRenderProps } from "../../agentkit/react/context.js";
|
|
30
30
|
import type { AgentMessageContentOrderResolver } from "../../agentkit/react/message-content-order.js";
|
|
31
|
+
import type {
|
|
32
|
+
AgentChatTranscriptLayout,
|
|
33
|
+
AgentChatWorkedForRowLayout,
|
|
34
|
+
} from "../../agentkit/react/transcript-layout.js";
|
|
31
35
|
import type { AssistantChatHistoryConfig } from "./history-types.js";
|
|
32
36
|
import type { BuilderSetupCardLayout } from "./run-recovery.js";
|
|
33
37
|
|
|
@@ -238,6 +242,12 @@ export interface AssistantChatProps {
|
|
|
238
242
|
/** Optional content rendered in the empty state, below the suggestion
|
|
239
243
|
* buttons. Unlike `threadFooterSlot` this never survives the first message. */
|
|
240
244
|
emptyStateFooter?: React.ReactNode;
|
|
245
|
+
/** Optional transcript geometry. Omitted values keep the shared defaults. */
|
|
246
|
+
transcriptLayout?: AgentChatTranscriptLayout;
|
|
247
|
+
/** Optional elapsed-work row geometry. Omitted values keep the shared defaults. */
|
|
248
|
+
workedForRowLayout?: AgentChatWorkedForRowLayout;
|
|
249
|
+
/** Optional duration text for the shared completed-work row. */
|
|
250
|
+
formatWorkedForDuration?: (durationMs: number) => string;
|
|
241
251
|
/** Whether to show the header bar. Default: true */
|
|
242
252
|
showHeader?: boolean;
|
|
243
253
|
/** CSS class for the outer container */
|
|
@@ -2698,9 +2698,17 @@ describe("WorkedForSummary", () => {
|
|
|
2698
2698
|
|
|
2699
2699
|
beforeEach(() => {
|
|
2700
2700
|
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
2701
|
+
builderHandoffMocks.useAgentChatContext.mockReturnValue({ items: [] });
|
|
2701
2702
|
container = document.createElement("div");
|
|
2702
2703
|
document.body.appendChild(container);
|
|
2703
2704
|
root = createRoot(container);
|
|
2705
|
+
const render = root.render.bind(root);
|
|
2706
|
+
root.render = (children: ReactNode) =>
|
|
2707
|
+
render(
|
|
2708
|
+
<AgentNativeI18nProvider persistPreference={false}>
|
|
2709
|
+
{children}
|
|
2710
|
+
</AgentNativeI18nProvider>,
|
|
2711
|
+
);
|
|
2704
2712
|
});
|
|
2705
2713
|
|
|
2706
2714
|
afterEach(() => {
|
|
@@ -2740,6 +2748,17 @@ describe("WorkedForSummary", () => {
|
|
|
2740
2748
|
response: ["The query returned a 12% increase."],
|
|
2741
2749
|
},
|
|
2742
2750
|
}}
|
|
2751
|
+
workedForRowLayout={{
|
|
2752
|
+
chevronSize: 16,
|
|
2753
|
+
iconGap: 4,
|
|
2754
|
+
fontSize: 14,
|
|
2755
|
+
lineHeight: 20,
|
|
2756
|
+
paddingBlock: 12,
|
|
2757
|
+
paddingInline: 12,
|
|
2758
|
+
}}
|
|
2759
|
+
formatWorkedForDuration={(durationMs) =>
|
|
2760
|
+
`${durationMs / 1000} seconds`
|
|
2761
|
+
}
|
|
2743
2762
|
/>,
|
|
2744
2763
|
);
|
|
2745
2764
|
});
|
|
@@ -2748,6 +2767,8 @@ describe("WorkedForSummary", () => {
|
|
|
2748
2767
|
container.querySelectorAll<HTMLButtonElement>("button[aria-expanded]"),
|
|
2749
2768
|
).find((button) => button.textContent?.includes("Worked for"));
|
|
2750
2769
|
expect(workedFor).not.toBeNull();
|
|
2770
|
+
expect(workedFor?.style.fontSize).toBe("14px");
|
|
2771
|
+
expect(workedFor?.textContent).toContain("Worked for 5 seconds");
|
|
2751
2772
|
act(() => workedFor?.click());
|
|
2752
2773
|
|
|
2753
2774
|
expect(container.textContent).not.toContain("This private thought");
|
|
@@ -2795,6 +2816,68 @@ describe("WorkedForSummary", () => {
|
|
|
2795
2816
|
expect(container.textContent).toContain("Worked for 5m");
|
|
2796
2817
|
});
|
|
2797
2818
|
|
|
2819
|
+
it("keeps the disclosure chevron before the duration label", () => {
|
|
2820
|
+
act(() => {
|
|
2821
|
+
root.render(
|
|
2822
|
+
<WorkedForSummary durationMs={5_000}>
|
|
2823
|
+
<div>Details</div>
|
|
2824
|
+
</WorkedForSummary>,
|
|
2825
|
+
);
|
|
2826
|
+
});
|
|
2827
|
+
|
|
2828
|
+
const button = container.querySelector("button[aria-expanded]");
|
|
2829
|
+
expect(button?.children[0]?.tagName.toLowerCase()).toBe("svg");
|
|
2830
|
+
expect(button?.children[1]?.textContent).toBe("Worked for 5s");
|
|
2831
|
+
const buttonClasses = button?.className.split(/\s+/) ?? [];
|
|
2832
|
+
expect(buttonClasses).toContain("gap-1");
|
|
2833
|
+
expect(buttonClasses).toContain("p-3");
|
|
2834
|
+
expect(buttonClasses).toContain("text-start");
|
|
2835
|
+
expect(buttonClasses).not.toContain("text-left");
|
|
2836
|
+
const chevron = button?.children[0];
|
|
2837
|
+
expect(chevron?.getAttribute("class")?.split(/\s+/)).toContain(
|
|
2838
|
+
"rtl:-scale-x-100",
|
|
2839
|
+
);
|
|
2840
|
+
|
|
2841
|
+
act(() => button?.click());
|
|
2842
|
+
|
|
2843
|
+
expect(chevron?.getAttribute("class")?.split(/\s+/)).toContain(
|
|
2844
|
+
"rtl:-rotate-90",
|
|
2845
|
+
);
|
|
2846
|
+
});
|
|
2847
|
+
|
|
2848
|
+
it("applies optional geometry to the completed-work disclosure row", () => {
|
|
2849
|
+
act(() => {
|
|
2850
|
+
root.render(
|
|
2851
|
+
<WorkedForSummary
|
|
2852
|
+
durationMs={5_000}
|
|
2853
|
+
layout={{
|
|
2854
|
+
chevronSize: 16,
|
|
2855
|
+
iconGap: 4,
|
|
2856
|
+
fontSize: 14,
|
|
2857
|
+
lineHeight: 20,
|
|
2858
|
+
paddingBlock: 12,
|
|
2859
|
+
paddingInline: 12,
|
|
2860
|
+
}}
|
|
2861
|
+
>
|
|
2862
|
+
<div>Details</div>
|
|
2863
|
+
</WorkedForSummary>,
|
|
2864
|
+
);
|
|
2865
|
+
});
|
|
2866
|
+
|
|
2867
|
+
const button = container.querySelector<HTMLButtonElement>(
|
|
2868
|
+
"button[aria-expanded]",
|
|
2869
|
+
);
|
|
2870
|
+
const icon = button?.querySelector<SVGElement>("svg");
|
|
2871
|
+
expect(button?.style.gap).toBe("4px");
|
|
2872
|
+
expect(button?.style.fontSize).toBe("14px");
|
|
2873
|
+
expect(button?.style.lineHeight).toBe("20px");
|
|
2874
|
+
expect(button?.style.paddingBlock).toBe("12px");
|
|
2875
|
+
expect(button?.style.paddingInline).toBe("12px");
|
|
2876
|
+
expect(button?.style.fontFamily).toBe("inherit");
|
|
2877
|
+
expect(icon?.style.width).toBe("16px");
|
|
2878
|
+
expect(icon?.style.height).toBe("16px");
|
|
2879
|
+
});
|
|
2880
|
+
|
|
2798
2881
|
it("applies the running shimmer while the work summary is live", () => {
|
|
2799
2882
|
act(() => {
|
|
2800
2883
|
root.render(
|