@agent-native/dispatch 0.25.1 → 0.27.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/actions/provider-api-register.d.ts +14 -14
- package/dist/components/create-app-popover.d.ts +7 -2
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +4 -3
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +42 -2
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/simple-agents-panel.d.ts +4 -1
- package/dist/components/simple-agents-panel.d.ts.map +1 -1
- package/dist/components/simple-agents-panel.js +46 -13
- package/dist/components/simple-agents-panel.js.map +1 -1
- package/dist/components/workspace-app-host.d.ts +3 -1
- package/dist/components/workspace-app-host.d.ts.map +1 -1
- package/dist/components/workspace-app-host.js +87 -5
- package/dist/components/workspace-app-host.js.map +1 -1
- package/package.json +3 -3
- package/src/components/create-app-popover.spec.tsx +9 -1
- package/src/components/create-app-popover.tsx +9 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/Layout.app-chat.spec.ts +22 -0
- package/src/components/layout/Layout.tsx +72 -3
- package/src/components/simple-agents-panel.spec.tsx +81 -1
- package/src/components/simple-agents-panel.tsx +156 -71
- package/src/components/workspace-app-host.spec.tsx +57 -0
- package/src/components/workspace-app-host.tsx +139 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AgentSidebar } from "@agent-native/core/client/agent-chat";
|
|
1
2
|
import {
|
|
2
3
|
ChatFirstAppPane,
|
|
3
4
|
defaultChatFirstCopy,
|
|
@@ -10,8 +11,14 @@ import {
|
|
|
10
11
|
} from "@agent-native/core/client/hooks";
|
|
11
12
|
import { useT } from "@agent-native/core/client/i18n";
|
|
12
13
|
import { withBuilderUtmTrackingParams } from "@agent-native/core/shared/builder-link-tracking";
|
|
13
|
-
import {
|
|
14
|
-
|
|
14
|
+
import {
|
|
15
|
+
IconArrowLeft,
|
|
16
|
+
IconArrowUpRight,
|
|
17
|
+
IconClockHour4,
|
|
18
|
+
IconLock,
|
|
19
|
+
} from "@tabler/icons-react";
|
|
20
|
+
import { useTheme } from "next-themes";
|
|
21
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
15
22
|
import { Link } from "react-router";
|
|
16
23
|
|
|
17
24
|
import { isEmbedSessionExpiredMessage } from "../lib/embed-session-recovery";
|
|
@@ -38,6 +45,40 @@ interface EmbedSessionInput {
|
|
|
38
45
|
chrome: "minimal";
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
type WorkspaceAppTheme = "light" | "dark";
|
|
49
|
+
type WorkspaceAppAuthState = "unknown" | "authenticated" | "unauthenticated";
|
|
50
|
+
|
|
51
|
+
function resolveWorkspaceAppAuthState(
|
|
52
|
+
rawUrl: string | null,
|
|
53
|
+
): WorkspaceAppAuthState {
|
|
54
|
+
if (!rawUrl) return "unknown";
|
|
55
|
+
try {
|
|
56
|
+
const lastSegment = new URL(rawUrl, window.location.origin).pathname
|
|
57
|
+
.split("/")
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.at(-1)
|
|
60
|
+
?.toLowerCase();
|
|
61
|
+
if (
|
|
62
|
+
lastSegment === "sign-in" ||
|
|
63
|
+
lastSegment === "login" ||
|
|
64
|
+
lastSegment === "signup"
|
|
65
|
+
) {
|
|
66
|
+
return "unauthenticated";
|
|
67
|
+
}
|
|
68
|
+
return "authenticated";
|
|
69
|
+
} catch {
|
|
70
|
+
return "unknown";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildWorkspaceAppThemeUpdate(theme: WorkspaceAppTheme) {
|
|
75
|
+
return {
|
|
76
|
+
type: "agent-native-theme-update" as const,
|
|
77
|
+
theme,
|
|
78
|
+
isDark: theme === "dark",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
41
82
|
export function buildChatFirstEmbedSessionInput(
|
|
42
83
|
appId: string,
|
|
43
84
|
path: string,
|
|
@@ -56,18 +97,49 @@ interface WorkspaceAppFrameProps {
|
|
|
56
97
|
app: WorkspaceAppFrameApp;
|
|
57
98
|
/** Chat-first app tabs use their own route while standalone hosts use app metadata. */
|
|
58
99
|
embedPath?: string;
|
|
100
|
+
/** Chat-first app surfaces own the parent chat rail around the iframe. */
|
|
101
|
+
chatSidebar?: boolean;
|
|
59
102
|
copy?: ChatFirstCopy;
|
|
60
103
|
}
|
|
61
104
|
|
|
62
105
|
export function WorkspaceAppFrame({
|
|
63
106
|
app,
|
|
64
107
|
embedPath,
|
|
108
|
+
chatSidebar = false,
|
|
65
109
|
copy = defaultChatFirstCopy,
|
|
66
110
|
}: WorkspaceAppFrameProps) {
|
|
111
|
+
const { resolvedTheme } = useTheme();
|
|
112
|
+
const theme: WorkspaceAppTheme =
|
|
113
|
+
resolvedTheme === "dark" || resolvedTheme === "light"
|
|
114
|
+
? resolvedTheme
|
|
115
|
+
: typeof document !== "undefined" &&
|
|
116
|
+
document.documentElement.classList.contains("dark")
|
|
117
|
+
? "dark"
|
|
118
|
+
: "light";
|
|
67
119
|
const [embedUrl, setEmbedUrl] = useState<string | null>(null);
|
|
68
120
|
const [embedError, setEmbedError] = useState<Error | null>(null);
|
|
69
121
|
const [embedAttempt, setEmbedAttempt] = useState(0);
|
|
122
|
+
const [authState, setAuthState] = useState<WorkspaceAppAuthState>("unknown");
|
|
70
123
|
const embedFrameRef = useRef<HTMLIFrameElement>(null);
|
|
124
|
+
const postThemeToFrame = useCallback(() => {
|
|
125
|
+
embedFrameRef.current?.contentWindow?.postMessage(
|
|
126
|
+
buildWorkspaceAppThemeUpdate(theme),
|
|
127
|
+
"*",
|
|
128
|
+
);
|
|
129
|
+
}, [theme]);
|
|
130
|
+
const handleFrameLoad = useCallback(() => {
|
|
131
|
+
postThemeToFrame();
|
|
132
|
+
const frame = embedFrameRef.current;
|
|
133
|
+
let frameUrl = embedUrl;
|
|
134
|
+
try {
|
|
135
|
+
frameUrl = frame?.contentWindow?.location.href ?? frameUrl;
|
|
136
|
+
// coercion-ok: Cross-origin frames cannot expose location; their auth state arrives by message.
|
|
137
|
+
} catch {
|
|
138
|
+
// Cross-origin app frames report their auth state through postMessage.
|
|
139
|
+
}
|
|
140
|
+
const nextAuthState = resolveWorkspaceAppAuthState(frameUrl);
|
|
141
|
+
if (nextAuthState !== "unknown") setAuthState(nextAuthState);
|
|
142
|
+
}, [embedUrl, postThemeToFrame]);
|
|
71
143
|
const workspaceSsoEnabled = useFeatureFlag(DISPATCH_WORKSPACE_SSO_FLAG.key);
|
|
72
144
|
const createEmbedSession = useActionMutation<
|
|
73
145
|
EmbedSessionResult,
|
|
@@ -104,6 +176,7 @@ export function WorkspaceAppFrame({
|
|
|
104
176
|
let cancelled = false;
|
|
105
177
|
setEmbedUrl(null);
|
|
106
178
|
setEmbedError(null);
|
|
179
|
+
setAuthState("unknown");
|
|
107
180
|
const createSession = workspaceSsoEnabled
|
|
108
181
|
? createWorkspaceSsoEmbedSession
|
|
109
182
|
: createEmbedSession;
|
|
@@ -154,7 +227,26 @@ export function WorkspaceAppFrame({
|
|
|
154
227
|
window.removeEventListener("message", handleEmbedSessionExpired);
|
|
155
228
|
}, [embedUrl]);
|
|
156
229
|
|
|
157
|
-
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
const handleAuthState = (event: MessageEvent) => {
|
|
232
|
+
const frame = embedFrameRef.current;
|
|
233
|
+
if (!frame || event.source !== frame.contentWindow) return;
|
|
234
|
+
if (event.data?.type !== "agentNative.authState") return;
|
|
235
|
+
const status = event.data.data?.status;
|
|
236
|
+
if (status === "authenticated" || status === "unauthenticated") {
|
|
237
|
+
setAuthState(status);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
window.addEventListener("message", handleAuthState);
|
|
242
|
+
return () => window.removeEventListener("message", handleAuthState);
|
|
243
|
+
}, []);
|
|
244
|
+
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
postThemeToFrame();
|
|
247
|
+
}, [embedUrl, postThemeToFrame]);
|
|
248
|
+
|
|
249
|
+
const appPane = (
|
|
158
250
|
<ChatFirstAppPane
|
|
159
251
|
app={app}
|
|
160
252
|
status={
|
|
@@ -178,6 +270,7 @@ export function WorkspaceAppFrame({
|
|
|
178
270
|
src={url}
|
|
179
271
|
title={title ?? app.name}
|
|
180
272
|
ref={embedFrameRef}
|
|
273
|
+
onLoad={handleFrameLoad}
|
|
181
274
|
referrerPolicy="no-referrer"
|
|
182
275
|
allow="clipboard-read; clipboard-write"
|
|
183
276
|
className="h-full w-full border-0 bg-background"
|
|
@@ -186,6 +279,49 @@ export function WorkspaceAppFrame({
|
|
|
186
279
|
copy={copy}
|
|
187
280
|
/>
|
|
188
281
|
);
|
|
282
|
+
|
|
283
|
+
if (!chatSidebar) return appPane;
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<AgentSidebar
|
|
287
|
+
position="left"
|
|
288
|
+
defaultOpen
|
|
289
|
+
openStorageKey="dispatch-app-chat"
|
|
290
|
+
storageKey={`dispatch-app-chat:${app.id}`}
|
|
291
|
+
scope={{
|
|
292
|
+
type: "workspace-app",
|
|
293
|
+
id: app.id,
|
|
294
|
+
label: app.name,
|
|
295
|
+
contextKey: `workspace-app:${app.id}`,
|
|
296
|
+
}}
|
|
297
|
+
agentChatSurface="app"
|
|
298
|
+
showTabBar
|
|
299
|
+
suppressInlineOpenApp
|
|
300
|
+
dynamicSuggestions={false}
|
|
301
|
+
suggestions={[]}
|
|
302
|
+
emptyStateText={`Ask about ${app.name}`}
|
|
303
|
+
composerSlot={
|
|
304
|
+
authState === "unauthenticated" ? (
|
|
305
|
+
<div className="flex shrink-0 items-center px-3 pb-1">
|
|
306
|
+
<button
|
|
307
|
+
type="button"
|
|
308
|
+
data-dispatch-app-sign-in
|
|
309
|
+
aria-label={`Sign in to ${app.name} on the right`}
|
|
310
|
+
title={`Sign in to ${app.name} on the right`}
|
|
311
|
+
onClick={() => embedFrameRef.current?.focus()}
|
|
312
|
+
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-border/70 bg-background/60 px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
313
|
+
>
|
|
314
|
+
<IconLock size={12} stroke={1.8} />
|
|
315
|
+
<span>Sign in on the right</span>
|
|
316
|
+
<IconArrowUpRight size={12} stroke={1.8} />
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
) : null
|
|
320
|
+
}
|
|
321
|
+
>
|
|
322
|
+
{appPane}
|
|
323
|
+
</AgentSidebar>
|
|
324
|
+
);
|
|
189
325
|
}
|
|
190
326
|
|
|
191
327
|
export function WorkspaceAppHost({ appId }: { appId?: string }) {
|