@agent-native/toolkit 0.214.0 → 0.217.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/agent-native.eject.json +5 -0
- 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/auth/AuthPage.d.ts.map +1 -1
- package/dist/app/auth/AuthPage.js +33 -44
- package/dist/app/auth/AuthPage.js.map +1 -1
- 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/app/onboarding/GettingStartedCard.d.ts +35 -0
- package/dist/app/onboarding/GettingStartedCard.d.ts.map +1 -0
- package/dist/app/onboarding/GettingStartedCard.js +38 -0
- package/dist/app/onboarding/GettingStartedCard.js.map +1 -0
- package/dist/app/onboarding/index.d.ts +1 -0
- package/dist/app/onboarding/index.d.ts.map +1 -1
- package/dist/app/onboarding/index.js +1 -0
- package/dist/app/onboarding/index.js.map +1 -1
- package/dist/app/providers/AppProviders.d.ts.map +1 -1
- package/dist/app/providers/AppProviders.js +3 -1
- package/dist/app/providers/AppProviders.js.map +1 -1
- package/dist/app/settings/AccountSettingsCard.d.ts.map +1 -1
- package/dist/app/settings/AccountSettingsCard.js +2 -1
- package/dist/app/settings/AccountSettingsCard.js.map +1 -1
- package/dist/app/settings/index.d.ts +1 -0
- package/dist/app/settings/index.d.ts.map +1 -1
- package/dist/app/settings/index.js +1 -0
- package/dist/app/settings/index.js.map +1 -1
- package/dist/app/settings/shell/SettingsShell.d.ts +40 -1
- package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
- package/dist/app/settings/shell/SettingsShell.js +87 -20
- package/dist/app/settings/shell/SettingsShell.js.map +1 -1
- package/dist/app/settings/shell/bridge.d.ts +5 -3
- package/dist/app/settings/shell/bridge.d.ts.map +1 -1
- package/dist/app/settings/shell/bridge.js +8 -5
- package/dist/app/settings/shell/bridge.js.map +1 -1
- package/dist/app/settings/shell/index.d.ts +2 -2
- package/dist/app/settings/shell/index.d.ts.map +1 -1
- package/dist/app/settings/shell/index.js.map +1 -1
- package/dist/app/settings/shell/registry.d.ts +9 -1
- package/dist/app/settings/shell/registry.d.ts.map +1 -1
- package/dist/app/settings/shell/registry.js +16 -0
- package/dist/app/settings/shell/registry.js.map +1 -1
- package/dist/app/settings/shell/routing.d.ts +5 -4
- package/dist/app/settings/shell/routing.d.ts.map +1 -1
- package/dist/app/settings/shell/routing.js +38 -10
- package/dist/app/settings/shell/routing.js.map +1 -1
- package/dist/app/shared/CommandMenu.d.ts +86 -6
- package/dist/app/shared/CommandMenu.d.ts.map +1 -1
- package/dist/app/shared/CommandMenu.js +303 -41
- package/dist/app/shared/CommandMenu.js.map +1 -1
- package/dist/app/shared/ConsentBanner.d.ts +9 -0
- package/dist/app/shared/ConsentBanner.d.ts.map +1 -0
- package/dist/app/shared/ConsentBanner.js +35 -0
- package/dist/app/shared/ConsentBanner.js.map +1 -0
- 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/dist/ui/sonner.d.ts +2 -2
- package/dist/ui/sonner.d.ts.map +1 -1
- package/dist/ui/sonner.js +2 -2
- package/dist/ui/sonner.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/auth/AuthPage.interaction.spec.tsx +74 -0
- package/src/app/auth/AuthPage.tsx +32 -39
- 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/app/onboarding/GettingStartedCard.tsx +203 -0
- package/src/app/onboarding/index.ts +1 -0
- package/src/app/providers/AppProviders.tsx +7 -1
- package/src/app/settings/AccountSettingsCard.spec.tsx +31 -0
- package/src/app/settings/AccountSettingsCard.tsx +8 -0
- package/src/app/settings/index.ts +1 -0
- package/src/app/settings/shell/SettingsShell.spec.tsx +472 -1
- package/src/app/settings/shell/SettingsShell.tsx +245 -31
- package/src/app/settings/shell/bridge.spec.ts +12 -0
- package/src/app/settings/shell/bridge.ts +17 -4
- package/src/app/settings/shell/index.ts +6 -1
- package/src/app/settings/shell/registry.ts +33 -1
- package/src/app/settings/shell/routing.spec.ts +92 -1
- package/src/app/settings/shell/routing.ts +52 -9
- package/src/app/shared/CommandMenu.spec.tsx +1662 -1
- package/src/app/shared/CommandMenu.tsx +763 -164
- package/src/app/shared/ConsentBanner.spec.tsx +141 -0
- package/src/app/shared/ConsentBanner.tsx +141 -0
- package/src/composer/ComposerContextBanner.spec.tsx +31 -5
- package/src/composer/ComposerContextBanner.tsx +7 -2
- package/src/ui/sonner.spec.ts +10 -0
- package/src/ui/sonner.tsx +2 -2
|
@@ -247,6 +247,80 @@ describe("AuthPage interactions", () => {
|
|
|
247
247
|
}
|
|
248
248
|
});
|
|
249
249
|
|
|
250
|
+
it("stores no visitor id on the sign-in page before analytics consent", async () => {
|
|
251
|
+
const originalHref = window.location.href;
|
|
252
|
+
const sendBeacon = vi.fn(() => true);
|
|
253
|
+
Object.defineProperty(navigator, "sendBeacon", {
|
|
254
|
+
value: sendBeacon,
|
|
255
|
+
configurable: true,
|
|
256
|
+
});
|
|
257
|
+
window.happyDOM.setURL("https://clips.agent-native.com/signup");
|
|
258
|
+
// Earlier renders in this file already settled the region.
|
|
259
|
+
Reflect.deleteProperty(
|
|
260
|
+
globalThis,
|
|
261
|
+
Symbol.for("agent-native.client.consent"),
|
|
262
|
+
);
|
|
263
|
+
window.localStorage.removeItem("agent-native.anonymous_id");
|
|
264
|
+
document.cookie = "an_aid=; path=/; max-age=0";
|
|
265
|
+
window.sessionStorage.setItem("an_consent_region", "consent");
|
|
266
|
+
(
|
|
267
|
+
window as Window & { __AGENT_NATIVE_CONFIG__?: Record<string, string> }
|
|
268
|
+
).__AGENT_NATIVE_CONFIG__ = {
|
|
269
|
+
agentNativeAnalyticsPublicKey: "anpk_test",
|
|
270
|
+
agentNativeAnalyticsEndpoint: "https://analytics.agent-native.com/track",
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
await act(async () => {
|
|
275
|
+
root.render(
|
|
276
|
+
<AuthPage
|
|
277
|
+
{...propsFromHtml(
|
|
278
|
+
getOnboardingHtml({
|
|
279
|
+
requestHost: "clips.agent-native.com",
|
|
280
|
+
requestPath: "/signup",
|
|
281
|
+
}),
|
|
282
|
+
)}
|
|
283
|
+
initialView="signup"
|
|
284
|
+
/>,
|
|
285
|
+
);
|
|
286
|
+
await Promise.resolve();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const email = container.querySelector<HTMLInputElement>("#s-email");
|
|
290
|
+
await act(async () => {
|
|
291
|
+
Object.getOwnPropertyDescriptor(
|
|
292
|
+
HTMLInputElement.prototype,
|
|
293
|
+
"value",
|
|
294
|
+
)!.set!.call(email, "person@example.com");
|
|
295
|
+
email!.dispatchEvent(new Event("input", { bubbles: true }));
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(sendBeacon).toHaveBeenCalled();
|
|
299
|
+
for (const [, body] of sendBeacon.mock.calls as unknown as [
|
|
300
|
+
string,
|
|
301
|
+
string,
|
|
302
|
+
][]) {
|
|
303
|
+
const sent = JSON.parse(body);
|
|
304
|
+
expect(sent.anonymousId).toBeUndefined();
|
|
305
|
+
expect(sent.properties.analytics_consent).toBe("withheld");
|
|
306
|
+
}
|
|
307
|
+
expect(window.localStorage.getItem("agent-native.anonymous_id")).toBe(
|
|
308
|
+
null,
|
|
309
|
+
);
|
|
310
|
+
expect(document.cookie).not.toMatch(/an_aid=[^;]/);
|
|
311
|
+
} finally {
|
|
312
|
+
delete (window as Window & { __AGENT_NATIVE_CONFIG__?: unknown })
|
|
313
|
+
.__AGENT_NATIVE_CONFIG__;
|
|
314
|
+
window.sessionStorage.removeItem("an_consent_region");
|
|
315
|
+
Reflect.deleteProperty(
|
|
316
|
+
globalThis,
|
|
317
|
+
Symbol.for("agent-native.client.consent"),
|
|
318
|
+
);
|
|
319
|
+
Reflect.deleteProperty(navigator, "sendBeacon");
|
|
320
|
+
window.happyDOM.setURL(originalHref);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
250
324
|
it("sends a beta app's sign-up events to beta Analytics", async () => {
|
|
251
325
|
const originalHref = window.location.href;
|
|
252
326
|
const sendBeacon = vi.fn(() => true);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
captureAttribution,
|
|
5
|
+
getAnalyticsAnonymousId,
|
|
5
6
|
getAnalyticsSessionId,
|
|
6
7
|
} from "@agent-native/core/client/analytics";
|
|
7
8
|
import { frameworkRoutePrefix } from "@agent-native/core/client/api-path";
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
normalizeOAuthReturnPath,
|
|
13
14
|
shouldRetryAuthSessionProbe,
|
|
14
15
|
} from "@agent-native/core/client/auth/auth-page-helpers";
|
|
16
|
+
import { whenConsentDecided } from "@agent-native/core/client/consent";
|
|
15
17
|
import { openOAuthPopup } from "@agent-native/core/client/oauth-popup";
|
|
16
18
|
import { navigateForSession } from "@agent-native/core/client/use-session";
|
|
17
19
|
import { normalizeLocaleCode } from "@agent-native/core/localization/shared";
|
|
@@ -65,7 +67,6 @@ type AuthRequestResult = {
|
|
|
65
67
|
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
66
68
|
const TAB_STORAGE_KEY = "an.onboarding.tab";
|
|
67
69
|
const PENDING_SIGNUP_EMAIL_STORAGE_KEY = "an.onboarding.pendingSignupEmail";
|
|
68
|
-
const ANALYTICS_ANONYMOUS_ID_KEY = "agent-native.anonymous_id";
|
|
69
70
|
const GOOGLE_AUTH_URL_PATH = "/_agent-native/google/auth-url";
|
|
70
71
|
const BUILDER_DESKTOP_RETURN_ORIGIN = "http://127.0.0.1:8080";
|
|
71
72
|
const useIsomorphicLayoutEffect =
|
|
@@ -261,15 +262,6 @@ async function requestJson(
|
|
|
261
262
|
return { response, data, readable };
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
function generateAnonymousId(): string {
|
|
265
|
-
try {
|
|
266
|
-
if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
267
|
-
} catch {
|
|
268
|
-
// coercion-ok: the browser fallback remains usable when crypto is unavailable.
|
|
269
|
-
}
|
|
270
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
265
|
function trackAuth(
|
|
274
266
|
app: string,
|
|
275
267
|
name: string,
|
|
@@ -298,9 +290,8 @@ function trackAuth(
|
|
|
298
290
|
}
|
|
299
291
|
).__AGENT_NATIVE_CONFIG__;
|
|
300
292
|
if (!config?.agentNativeAnalyticsPublicKey) return;
|
|
301
|
-
const anonymousId = readStorage(ANALYTICS_ANONYMOUS_ID_KEY);
|
|
302
|
-
if (!anonymousId) return;
|
|
303
293
|
const sessionId = getAnalyticsSessionId();
|
|
294
|
+
const timestamp = new Date().toISOString();
|
|
304
295
|
const endpoint = resolveLaneEndpoint(
|
|
305
296
|
config.agentNativeAnalyticsEndpoint ??
|
|
306
297
|
"https://analytics.agent-native.com/track",
|
|
@@ -313,23 +304,33 @@ function trackAuth(
|
|
|
313
304
|
}> = [{ name, properties: legacyProperties }];
|
|
314
305
|
const canonical = canonicalTrackingEvent(name, legacyProperties);
|
|
315
306
|
if (canonical) events.push(canonical);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
307
|
+
const publicKey = config.agentNativeAnalyticsPublicKey;
|
|
308
|
+
// The visitor id waits for analytics consent, as in the app bundle.
|
|
309
|
+
whenConsentDecided((decided) => {
|
|
310
|
+
const anonymousId = decided ? getAnalyticsAnonymousId() : undefined;
|
|
311
|
+
for (const event of events) {
|
|
312
|
+
const body = JSON.stringify({
|
|
313
|
+
publicKey,
|
|
314
|
+
event: event.name,
|
|
315
|
+
properties: anonymousId
|
|
316
|
+
? event.properties
|
|
317
|
+
: {
|
|
318
|
+
...event.properties,
|
|
319
|
+
analytics_consent: decided ? "withheld" : "unknown",
|
|
320
|
+
},
|
|
321
|
+
anonymousId,
|
|
322
|
+
sessionId,
|
|
323
|
+
timestamp,
|
|
324
|
+
});
|
|
325
|
+
if (navigator.sendBeacon?.(endpoint, body)) continue;
|
|
326
|
+
void fetch(endpoint, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
body,
|
|
329
|
+
keepalive: true,
|
|
330
|
+
headers: { "Content-Type": "text/plain;charset=UTF-8" },
|
|
331
|
+
}).catch(() => undefined);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
333
334
|
} catch {
|
|
334
335
|
// coercion-ok: analytics is best effort and cannot block authentication.
|
|
335
336
|
}
|
|
@@ -1036,16 +1037,8 @@ export function AuthPage(props: AuthPageProps) {
|
|
|
1036
1037
|
}, [apiPath, redirectToSignedInApp, runtimeBasePathResolved, view]);
|
|
1037
1038
|
|
|
1038
1039
|
React.useEffect(() => {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
anonymousId = generateAnonymousId();
|
|
1042
|
-
writeStorage(ANALYTICS_ANONYMOUS_ID_KEY, anonymousId);
|
|
1043
|
-
}
|
|
1044
|
-
try {
|
|
1045
|
-
document.cookie = `an_aid=${encodeURIComponent(anonymousId)}; path=/; max-age=2592000; SameSite=Lax`;
|
|
1046
|
-
} catch {
|
|
1047
|
-
// coercion-ok: attribution cookies are optional for authentication.
|
|
1048
|
-
}
|
|
1040
|
+
// Stores `an_aid` only with analytics consent, and asks for the region.
|
|
1041
|
+
getAnalyticsAnonymousId();
|
|
1049
1042
|
// Capture is once per page load, so it must wait for a workspace's
|
|
1050
1043
|
// inferred base path or `landing_path` falls back to the wrong home.
|
|
1051
1044
|
if (runtimeBasePathResolved) {
|
|
@@ -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(
|