@agent-native/dispatch 0.20.3 → 0.21.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 +6 -6
- package/dist/actions/start-workspace-app-creation.js +1 -1
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/components/automation-run-history-dialog.d.ts.map +1 -1
- package/dist/components/automation-run-history-dialog.js +1 -0
- package/dist/components/automation-run-history-dialog.js.map +1 -1
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +5 -71
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +23 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +702 -15
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/lib/automation-display.d.ts +6 -0
- package/dist/lib/automation-display.d.ts.map +1 -1
- package/dist/lib/automation-display.js +8 -0
- package/dist/lib/automation-display.js.map +1 -1
- package/dist/lib/automations.d.ts +1 -0
- package/dist/lib/automations.d.ts.map +1 -1
- package/dist/lib/automations.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +6 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +21 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/pages/automations.d.ts.map +1 -1
- package/dist/routes/pages/automations.js +16 -6
- package/dist/routes/pages/automations.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +21 -1
- package/dist/routes/pages/settings.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +13 -0
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +1 -0
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/server/plugins/db.d.ts +1 -0
- package/dist/server/plugins/db.d.ts.map +1 -1
- package/dist/server/plugins/db.js +1 -1
- package/dist/server/plugins/db.js.map +1 -1
- package/dist/server/plugins/integrations.js +1 -1
- package/dist/server/plugins/integrations.js.map +1 -1
- package/package.json +3 -3
- package/src/actions/start-workspace-app-creation.ts +1 -1
- package/src/components/automation-run-history-dialog.tsx +1 -0
- package/src/components/create-app-popover.spec.tsx +22 -0
- package/src/components/create-app-popover.tsx +30 -94
- package/src/components/layout/Layout.spec.tsx +16 -1
- package/src/components/layout/Layout.tsx +1356 -34
- package/src/lib/automation-display.spec.ts +7 -0
- package/src/lib/automation-display.ts +11 -0
- package/src/lib/automations.ts +1 -0
- package/src/lib/workspace-apps.ts +24 -0
- package/src/routes/pages/automations.tsx +45 -6
- package/src/routes/pages/settings.tsx +72 -0
- package/src/server/index.ts +4 -1
- package/src/server/lib/app-creation-store.spec.ts +13 -0
- package/src/server/lib/app-creation-store.ts +13 -0
- package/src/server/lib/mcp-gateway.spec.ts +18 -0
- package/src/server/plugins/agent-chat.ts +1 -0
- package/src/server/plugins/db.ts +1 -1
- package/src/server/plugins/integrations.ts +1 -1
- package/src/styles/dispatch.css +29 -0
|
@@ -1,13 +1,65 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CHAT_FIRST_MODE_CHANGED_EVENT,
|
|
3
|
+
AgentChatSurface,
|
|
4
|
+
chatFirstSurfaceTabId,
|
|
2
5
|
AgentSidebar,
|
|
6
|
+
ChatFirstSurfacePanelToggle,
|
|
7
|
+
closeChatFirstSessionWatch,
|
|
8
|
+
emitChatFirstSessionWatch,
|
|
9
|
+
getChatFirstSurfaceTabsStore,
|
|
3
10
|
focusAgentChat,
|
|
4
11
|
navigateWithAgentChatViewTransition,
|
|
12
|
+
readChatFirstAppLayout,
|
|
13
|
+
readChatFirstMode,
|
|
14
|
+
resolveChatFirstBrowserTarget,
|
|
15
|
+
resolveChatFirstAppTarget,
|
|
16
|
+
subscribeChatFirstOpenBrowser,
|
|
17
|
+
subscribeChatFirstOpenApp,
|
|
18
|
+
writeChatFirstAppLayout,
|
|
19
|
+
useChatFirstSessionWatch,
|
|
20
|
+
useChatFirstSurfacePanel,
|
|
21
|
+
useChatFirstSurfaceResize,
|
|
22
|
+
useChatFirstSurfaceTabs,
|
|
23
|
+
type ChatFirstAppRegistration,
|
|
24
|
+
type ChatFirstAppLayoutPreference,
|
|
25
|
+
type ChatFirstAppResolution,
|
|
26
|
+
type ChatFirstOpenBrowserDetail,
|
|
27
|
+
type ChatFirstOpenAppDetail,
|
|
28
|
+
type ChatFirstSessionReference,
|
|
29
|
+
type ChatFirstSurfaceTab,
|
|
30
|
+
type ChatFirstSurfaceKind,
|
|
5
31
|
useAgentChatHomeHandoff,
|
|
6
32
|
useAgentChatHomeHandoffLinks,
|
|
7
33
|
useChatThreads,
|
|
8
34
|
type ChatThreadSummary,
|
|
9
35
|
} from "@agent-native/core/client/agent-chat";
|
|
10
36
|
import { appBasePath, appPath } from "@agent-native/core/client/api-path";
|
|
37
|
+
import {
|
|
38
|
+
readClientAppState,
|
|
39
|
+
writeClientAppState,
|
|
40
|
+
} from "@agent-native/core/client/application-state";
|
|
41
|
+
import {
|
|
42
|
+
ChatFirstAgentsPane,
|
|
43
|
+
ChatFirstAppPane,
|
|
44
|
+
ChatFirstAppsRail,
|
|
45
|
+
ChatFirstBrowserPane,
|
|
46
|
+
ChatFirstChatHistory,
|
|
47
|
+
ChatFirstPrimaryNavigation,
|
|
48
|
+
ChatFirstSessionWatchPane,
|
|
49
|
+
ChatFirstSurfacePanel,
|
|
50
|
+
ChatFirstSurfaceContent,
|
|
51
|
+
ChatFirstSurfaceTabs,
|
|
52
|
+
defaultChatFirstCopy,
|
|
53
|
+
type ChatFirstAgentActivity,
|
|
54
|
+
type ChatFirstAppItem,
|
|
55
|
+
type ChatFirstCopy,
|
|
56
|
+
type ChatFirstEmbedTarget,
|
|
57
|
+
} from "@agent-native/core/client/chat-first";
|
|
58
|
+
import { writeClipboardText } from "@agent-native/core/client/clipboard";
|
|
59
|
+
import {
|
|
60
|
+
useActionMutation,
|
|
61
|
+
useActionQuery,
|
|
62
|
+
} from "@agent-native/core/client/hooks";
|
|
11
63
|
import { useT } from "@agent-native/core/client/i18n";
|
|
12
64
|
import { openCommandMenu } from "@agent-native/core/client/navigation";
|
|
13
65
|
import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
|
|
@@ -21,6 +73,8 @@ import {
|
|
|
21
73
|
IconApps,
|
|
22
74
|
IconBrandSlack,
|
|
23
75
|
IconBrandTelegram,
|
|
76
|
+
IconCopy,
|
|
77
|
+
IconEye,
|
|
24
78
|
IconMessageQuestion,
|
|
25
79
|
IconBroadcast,
|
|
26
80
|
IconLayoutSidebarLeftCollapse,
|
|
@@ -30,6 +84,7 @@ import {
|
|
|
30
84
|
IconSearch,
|
|
31
85
|
IconWorld,
|
|
32
86
|
IconDeviceDesktop,
|
|
87
|
+
IconPlus,
|
|
33
88
|
} from "@tabler/icons-react";
|
|
34
89
|
import {
|
|
35
90
|
createContext,
|
|
@@ -38,12 +93,20 @@ import {
|
|
|
38
93
|
useRef,
|
|
39
94
|
useState,
|
|
40
95
|
useContext,
|
|
96
|
+
useCallback,
|
|
41
97
|
type ComponentType,
|
|
42
98
|
type ReactNode,
|
|
43
99
|
} from "react";
|
|
44
100
|
import { Link, useLocation, useNavigate } from "react-router";
|
|
101
|
+
import { toast } from "sonner";
|
|
45
102
|
|
|
46
103
|
import { cn } from "../../lib/utils";
|
|
104
|
+
import {
|
|
105
|
+
mergeChatFirstWorkspaceApps,
|
|
106
|
+
type WorkspaceAppSummary,
|
|
107
|
+
} from "../../lib/workspace-apps";
|
|
108
|
+
import { AppIcon } from "../app-icon";
|
|
109
|
+
import { CreateAppPopover } from "../create-app-popover";
|
|
47
110
|
import {
|
|
48
111
|
DropdownMenu,
|
|
49
112
|
DropdownMenuContent,
|
|
@@ -80,6 +143,8 @@ export interface DispatchNavItem {
|
|
|
80
143
|
}
|
|
81
144
|
|
|
82
145
|
export interface DispatchExtensionConfig {
|
|
146
|
+
/** Opt into the Codex/T3-like chat-first shell for chat routes. */
|
|
147
|
+
chatFirst?: boolean;
|
|
83
148
|
/** Extra sidebar tabs supplied by the generated workspace. */
|
|
84
149
|
navItems?: readonly DispatchNavItem[];
|
|
85
150
|
/** Extra React Query keys to invalidate when Dispatch receives DB sync events. */
|
|
@@ -131,6 +196,43 @@ const DISPATCH_SIDEBAR_LABEL = "Dispatch";
|
|
|
131
196
|
const CHROMELESS_PATHS = ["/approval", "/browser-chat", "/browser-connect"];
|
|
132
197
|
const SIDEBAR_COLLAPSE_KEY = "dispatch.sidebar.collapsed";
|
|
133
198
|
const CHAT_HISTORY_SOURCE_KEY = "dispatch.chat-history.source";
|
|
199
|
+
const CHAT_FIRST_PANE_STATE_KEY = "chat-first-pane";
|
|
200
|
+
|
|
201
|
+
interface DispatchChatFirstPane {
|
|
202
|
+
appId: string;
|
|
203
|
+
path?: string;
|
|
204
|
+
view?: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface ChatFirstEmbedSessionResult {
|
|
208
|
+
startUrl: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface ChatFirstEmbedSessionInput {
|
|
212
|
+
app?: string;
|
|
213
|
+
path?: string;
|
|
214
|
+
url?: string;
|
|
215
|
+
chrome: "minimal";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function buildChatFirstEmbedSessionInput(
|
|
219
|
+
appId: string,
|
|
220
|
+
path: string,
|
|
221
|
+
): ChatFirstEmbedSessionInput {
|
|
222
|
+
return { app: appId, path, chrome: "minimal" };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface DispatchAgentThreadSummary {
|
|
226
|
+
id: string;
|
|
227
|
+
title: string;
|
|
228
|
+
preview?: string;
|
|
229
|
+
snippet?: string;
|
|
230
|
+
updatedAt?: number;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface SearchAgentThreadsResult {
|
|
234
|
+
threads: DispatchAgentThreadSummary[];
|
|
235
|
+
}
|
|
134
236
|
|
|
135
237
|
const DispatchExtensionsContext = createContext<
|
|
136
238
|
DispatchExtensionConfig | undefined
|
|
@@ -199,8 +301,160 @@ function dispatchNavLinkTarget(path: string): string {
|
|
|
199
301
|
return routerHasBasename ? path : appPath(path);
|
|
200
302
|
}
|
|
201
303
|
|
|
202
|
-
function
|
|
203
|
-
|
|
304
|
+
function chatFirstResolutionMessage(
|
|
305
|
+
reason: Exclude<ChatFirstAppResolution, { status: "ready" }>["reason"],
|
|
306
|
+
): string {
|
|
307
|
+
switch (reason) {
|
|
308
|
+
case "empty-detail":
|
|
309
|
+
return "The agent did not provide an app target to open.";
|
|
310
|
+
case "invalid-url":
|
|
311
|
+
return "The requested app route was not registered for this workspace app.";
|
|
312
|
+
case "unknown-app":
|
|
313
|
+
return "That app is not available in this workspace.";
|
|
314
|
+
}
|
|
315
|
+
return "The requested app could not be opened.";
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function chatFirstBrowserResolutionMessage(
|
|
319
|
+
reason: "empty-detail" | "invalid-url",
|
|
320
|
+
): string {
|
|
321
|
+
return reason === "empty-detail"
|
|
322
|
+
? "The agent did not provide a browser URL to open."
|
|
323
|
+
: "The requested browser URL is not a safe HTTP(S) address.";
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const DISPATCH_CHAT_FIRST_COPY_KEYS: Record<string, string> = {
|
|
327
|
+
workspaceApps: "chatFirstWorkspaceApps",
|
|
328
|
+
createWorkspaceApp: "chatFirstCreateWorkspaceApp",
|
|
329
|
+
openApp: "chatFirstOpenApp",
|
|
330
|
+
appsLoadError: "chatFirstAppsLoadError",
|
|
331
|
+
noWorkspaceApps: "chatFirstNoWorkspaceApps",
|
|
332
|
+
createApp: "chatFirstCreateApp",
|
|
333
|
+
retry: "chatFirstRetry",
|
|
334
|
+
dismiss: "chatFirstDismiss",
|
|
335
|
+
unpinApp: "chatFirstUnpinApp",
|
|
336
|
+
pinApp: "chatFirstPinApp",
|
|
337
|
+
removePinned: "chatFirstRemovePinned",
|
|
338
|
+
pinTop: "chatFirstPinTop",
|
|
339
|
+
openSideSurfaces: "chatFirstOpenSideSurfaces",
|
|
340
|
+
closeTab: "chatFirstCloseTab",
|
|
341
|
+
close: "chatFirstClose",
|
|
342
|
+
closeOthers: "chatFirstCloseOthers",
|
|
343
|
+
closeToRight: "chatFirstCloseToRight",
|
|
344
|
+
closeAll: "chatFirstCloseAll",
|
|
345
|
+
unavailable: "chatFirstUnavailable",
|
|
346
|
+
openActivity: "chatFirstOpenActivity",
|
|
347
|
+
deferred: "chatFirstDeferred",
|
|
348
|
+
browserBack: "chatFirstBrowserBack",
|
|
349
|
+
browserForward: "chatFirstBrowserForward",
|
|
350
|
+
browserReload: "chatFirstBrowserReload",
|
|
351
|
+
browserAddress: "chatFirstBrowserAddress",
|
|
352
|
+
browserOpenExternal: "chatFirstBrowserOpenExternal",
|
|
353
|
+
browserClose: "chatFirstBrowserClose",
|
|
354
|
+
browserPage: "chatFirstBrowserPage",
|
|
355
|
+
browserInvalidUrl: "chatFirstBrowserInvalidUrl",
|
|
356
|
+
browserPreviewStarting: "chatFirstBrowserPreviewStarting",
|
|
357
|
+
browserPreviewError: "chatFirstBrowserPreviewError",
|
|
358
|
+
appUnavailable: "chatFirstAppUnavailable",
|
|
359
|
+
appLoading: "chatFirstLoadingApp",
|
|
360
|
+
agentActivityEyebrow: "chatFirstAgentActivityEyebrow",
|
|
361
|
+
agentActivityTitle: "chatFirstAgentActivityTitle",
|
|
362
|
+
agentActivityDescription: "chatFirstAgentActivityDescription",
|
|
363
|
+
refreshAgentActivity: "chatFirstRefreshAgentActivity",
|
|
364
|
+
noAgentSessions: "chatFirstNoAgentSessions",
|
|
365
|
+
startAgentSession: "chatFirstStartAgentSession",
|
|
366
|
+
watchSession: "chatFirstWatchSession",
|
|
367
|
+
copySessionId: "chatFirstCopySessionId",
|
|
368
|
+
copySessionIdFor: "chatFirstCopySessionIdFor",
|
|
369
|
+
sessionIdCopied: "chatFirstSessionIdCopied",
|
|
370
|
+
sessionIdShort: "chatFirstSessionIdShort",
|
|
371
|
+
copied: "chatFirstCopied",
|
|
372
|
+
agentActivityStatusQueued: "chatFirstAgentActivityStatusQueued",
|
|
373
|
+
agentActivityStatusRunning: "chatFirstAgentActivityStatusRunning",
|
|
374
|
+
agentActivityStatusPaused: "chatFirstAgentActivityStatusPaused",
|
|
375
|
+
agentActivityStatusNeedsApproval: "chatFirstAgentActivityStatusNeedsApproval",
|
|
376
|
+
agentActivityStatusCompleted: "chatFirstAgentActivityStatusCompleted",
|
|
377
|
+
agentActivityStatusErrored: "chatFirstAgentActivityStatusErrored",
|
|
378
|
+
agentActivityStatusRecent: "chatFirstAgentActivityStatusRecent",
|
|
379
|
+
agentActivityStatusUnknown: "chatFirstAgentActivityStatusUnknown",
|
|
380
|
+
watchingSession: "chatFirstWatchingSession",
|
|
381
|
+
session: "chatFirstSession",
|
|
382
|
+
stopWatchingSession: "chatFirstStopWatchingSession",
|
|
383
|
+
stopWatching: "chatFirstStopWatching",
|
|
384
|
+
watchedSession: "chatFirstWatchedSession",
|
|
385
|
+
agentsTitle: "chatFirstAgentsTitle",
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const DISPATCH_CHAT_FIRST_SURFACE_COPY_KEYS: Record<
|
|
389
|
+
string,
|
|
390
|
+
{ label: string; reason: string }
|
|
391
|
+
> = {
|
|
392
|
+
browser: {
|
|
393
|
+
label: "chatFirstSurfaceBrowserLabel",
|
|
394
|
+
reason: "chatFirstSurfaceBrowserReason",
|
|
395
|
+
},
|
|
396
|
+
terminal: {
|
|
397
|
+
label: "chatFirstSurfaceTerminalLabel",
|
|
398
|
+
reason: "chatFirstSurfaceTerminalReason",
|
|
399
|
+
},
|
|
400
|
+
files: {
|
|
401
|
+
label: "chatFirstSurfaceFilesLabel",
|
|
402
|
+
reason: "chatFirstSurfaceFilesReason",
|
|
403
|
+
},
|
|
404
|
+
diff: {
|
|
405
|
+
label: "chatFirstSurfaceDiffLabel",
|
|
406
|
+
reason: "chatFirstSurfaceDiffReason",
|
|
407
|
+
},
|
|
408
|
+
"side-chat": {
|
|
409
|
+
label: "chatFirstSurfaceSideChatLabel",
|
|
410
|
+
reason: "chatFirstSurfaceSideChatReason",
|
|
411
|
+
},
|
|
412
|
+
agents: {
|
|
413
|
+
label: "chatFirstSurfaceAgentsLabel",
|
|
414
|
+
reason: "chatFirstSurfaceAgentsReason",
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
function createDispatchChatFirstCopy(
|
|
419
|
+
t: (key: string, options?: Record<string, unknown>) => string,
|
|
420
|
+
): ChatFirstCopy {
|
|
421
|
+
return (key, values) => {
|
|
422
|
+
if (key.startsWith("surface.")) {
|
|
423
|
+
const [, kind, field] = key.split(".");
|
|
424
|
+
const surface = DISPATCH_CHAT_FIRST_SURFACE_COPY_KEYS[kind];
|
|
425
|
+
const translationKey =
|
|
426
|
+
surface && (field === "label" || field === "reason")
|
|
427
|
+
? surface[field]
|
|
428
|
+
: undefined;
|
|
429
|
+
if (translationKey) {
|
|
430
|
+
return t(`dispatch.pages.${translationKey}`, {
|
|
431
|
+
...(values ?? {}),
|
|
432
|
+
defaultValue: defaultChatFirstCopy(key, values),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const translationKey = DISPATCH_CHAT_FIRST_COPY_KEYS[key];
|
|
437
|
+
if (!translationKey) return defaultChatFirstCopy(key, values);
|
|
438
|
+
return t(`dispatch.pages.${translationKey}`, {
|
|
439
|
+
...(values ?? {}),
|
|
440
|
+
defaultValue: defaultChatFirstCopy(key, values),
|
|
441
|
+
});
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function chatThreadPath(threadId: string | null): string {
|
|
446
|
+
return threadId ? `/chat/${encodeURIComponent(threadId)}` : "/chat";
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function persistedChatFirstPane(value: unknown): DispatchChatFirstPane | null {
|
|
450
|
+
if (!value || typeof value !== "object") return null;
|
|
451
|
+
const record = value as Record<string, unknown>;
|
|
452
|
+
if (typeof record.appId !== "string" || !record.appId.trim()) return null;
|
|
453
|
+
return {
|
|
454
|
+
appId: record.appId,
|
|
455
|
+
...(typeof record.path === "string" ? { path: record.path } : {}),
|
|
456
|
+
...(typeof record.view === "string" ? { view: record.view } : {}),
|
|
457
|
+
};
|
|
204
458
|
}
|
|
205
459
|
|
|
206
460
|
function threadIdFromPath(pathname: string): string | null {
|
|
@@ -261,7 +515,19 @@ function threadUpdatedAt(thread: ChatThreadSummary) {
|
|
|
261
515
|
: 0;
|
|
262
516
|
}
|
|
263
517
|
|
|
264
|
-
function DispatchChatsSection({
|
|
518
|
+
function DispatchChatsSection({
|
|
519
|
+
onNavigate,
|
|
520
|
+
showNewChat = false,
|
|
521
|
+
prelude,
|
|
522
|
+
chatFirstMode = false,
|
|
523
|
+
chatFirstEmbedded = false,
|
|
524
|
+
}: {
|
|
525
|
+
onNavigate?: () => void;
|
|
526
|
+
showNewChat?: boolean;
|
|
527
|
+
prelude?: ReactNode;
|
|
528
|
+
chatFirstMode?: boolean;
|
|
529
|
+
chatFirstEmbedded?: boolean;
|
|
530
|
+
}) {
|
|
265
531
|
const t = useT();
|
|
266
532
|
const navigate = useNavigate();
|
|
267
533
|
const location = useLocation();
|
|
@@ -390,7 +656,23 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
390
656
|
}
|
|
391
657
|
|
|
392
658
|
return (
|
|
393
|
-
<div
|
|
659
|
+
<div
|
|
660
|
+
className={cn(
|
|
661
|
+
"ms-4 min-w-0 space-y-0.5",
|
|
662
|
+
showNewChat && "dispatch-chat-first-chats",
|
|
663
|
+
)}
|
|
664
|
+
>
|
|
665
|
+
{showNewChat ? (
|
|
666
|
+
<button
|
|
667
|
+
type="button"
|
|
668
|
+
className="flex h-7 w-[calc(100%-0.5rem)] items-center gap-2 rounded-md px-2 text-sm text-sidebar-foreground/80 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
669
|
+
onClick={() => void handleNewChat()}
|
|
670
|
+
>
|
|
671
|
+
<IconPlus size={16} className="shrink-0" aria-hidden="true" />
|
|
672
|
+
<span>New chat</span>
|
|
673
|
+
</button>
|
|
674
|
+
) : null}
|
|
675
|
+
{prelude}
|
|
394
676
|
<div className="flex justify-end px-2 pt-0.5">
|
|
395
677
|
<Tooltip>
|
|
396
678
|
<TooltipTrigger asChild>
|
|
@@ -428,7 +710,8 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
428
710
|
</TooltipContent>
|
|
429
711
|
</Tooltip>
|
|
430
712
|
</div>
|
|
431
|
-
{
|
|
713
|
+
{!chatFirstMode &&
|
|
714
|
+
chatsLoading &&
|
|
432
715
|
visibleThreads.length === 0 &&
|
|
433
716
|
Array.from({ length: 3 }).map((_, index) => (
|
|
434
717
|
<div
|
|
@@ -439,31 +722,147 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
439
722
|
<Skeleton className="h-3 w-3/4 rounded" />
|
|
440
723
|
</div>
|
|
441
724
|
))}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
725
|
+
{chatFirstMode ? (
|
|
726
|
+
<ChatFirstChatHistory
|
|
727
|
+
items={chatItems}
|
|
728
|
+
activeId={displayedActiveThreadId}
|
|
729
|
+
loading={chatsLoading && visibleThreads.length === 0}
|
|
730
|
+
loadingLabel={
|
|
731
|
+
<div className="space-y-1 px-2 py-1">
|
|
732
|
+
{[0, 1, 2].map((index) => (
|
|
733
|
+
<div key={index} className="flex items-center gap-2 px-1 py-1">
|
|
734
|
+
<Skeleton className="size-3.5 shrink-0 rounded-sm" />
|
|
735
|
+
<Skeleton className="h-3 w-3/4 rounded" />
|
|
736
|
+
</div>
|
|
737
|
+
))}
|
|
738
|
+
</div>
|
|
739
|
+
}
|
|
740
|
+
label={t("dispatch.sidebar.chats", { defaultValue: "Chats" })}
|
|
741
|
+
onSelect={(threadId) => openThread(threadId)}
|
|
742
|
+
renameMaxLength={160}
|
|
743
|
+
onRename={(threadId, title) => void renameThread(threadId, title)}
|
|
744
|
+
labels={{
|
|
745
|
+
options: (item) =>
|
|
746
|
+
t("dispatch.sidebar.chatOptions", {
|
|
747
|
+
title: item.titleText ?? "",
|
|
748
|
+
}),
|
|
749
|
+
renameInput: (item) =>
|
|
750
|
+
t("dispatch.sidebar.renameThread", {
|
|
751
|
+
title: item.titleText ?? "",
|
|
752
|
+
}),
|
|
753
|
+
rename: t("dispatch.sidebar.renameChat"),
|
|
754
|
+
}}
|
|
755
|
+
renderAdditionalRowActions={(item, closeMenu) => (
|
|
756
|
+
<>
|
|
757
|
+
<button
|
|
758
|
+
type="button"
|
|
759
|
+
role="menuitem"
|
|
760
|
+
className="an-chat-history-row__menu-item"
|
|
761
|
+
onClick={() => {
|
|
762
|
+
closeMenu();
|
|
763
|
+
void writeClipboardText(item.id).then((copied) => {
|
|
764
|
+
toast[copied ? "success" : "error"](
|
|
765
|
+
copied
|
|
766
|
+
? "Session ID copied"
|
|
767
|
+
: "Could not copy the session ID",
|
|
768
|
+
);
|
|
769
|
+
});
|
|
770
|
+
}}
|
|
771
|
+
>
|
|
772
|
+
<IconCopy size={13} strokeWidth={1.8} />
|
|
773
|
+
<span>Copy session ID</span>
|
|
774
|
+
</button>
|
|
775
|
+
{!chatFirstEmbedded ? (
|
|
776
|
+
<button
|
|
777
|
+
type="button"
|
|
778
|
+
role="menuitem"
|
|
779
|
+
className="an-chat-history-row__menu-item"
|
|
780
|
+
onClick={() => {
|
|
781
|
+
closeMenu();
|
|
782
|
+
emitChatFirstSessionWatch({
|
|
783
|
+
sessionId: item.id,
|
|
784
|
+
title: item.titleText,
|
|
785
|
+
kind: "agent-chat",
|
|
786
|
+
sourceSessionId: displayedActiveThreadId ?? undefined,
|
|
787
|
+
});
|
|
788
|
+
}}
|
|
789
|
+
>
|
|
790
|
+
<IconEye size={13} strokeWidth={1.8} />
|
|
791
|
+
<span>Watch and message session</span>
|
|
792
|
+
</button>
|
|
793
|
+
) : null}
|
|
794
|
+
</>
|
|
795
|
+
)}
|
|
796
|
+
className="min-w-0"
|
|
797
|
+
/>
|
|
798
|
+
) : (
|
|
799
|
+
<ChatHistoryRail
|
|
800
|
+
items={chatItems}
|
|
801
|
+
activeId={displayedActiveThreadId}
|
|
802
|
+
onSelect={(threadId) => openThread(threadId)}
|
|
803
|
+
onNewChat={() => void handleNewChat()}
|
|
804
|
+
railLabels={{
|
|
805
|
+
newChat: t("dispatch.sidebar.newChat"),
|
|
806
|
+
showMore: t("dispatch.sidebar.chats"),
|
|
807
|
+
showLess: t("dispatch.sidebar.chats"),
|
|
808
|
+
}}
|
|
809
|
+
renameMaxLength={160}
|
|
810
|
+
onRename={(threadId, title) => void renameThread(threadId, title)}
|
|
811
|
+
labels={{
|
|
812
|
+
options: (item) =>
|
|
813
|
+
t("dispatch.sidebar.chatOptions", {
|
|
814
|
+
title: item.titleText ?? "",
|
|
815
|
+
}),
|
|
816
|
+
renameInput: (item) =>
|
|
817
|
+
t("dispatch.sidebar.renameThread", {
|
|
818
|
+
title: item.titleText ?? "",
|
|
819
|
+
}),
|
|
820
|
+
rename: t("dispatch.sidebar.renameChat"),
|
|
821
|
+
}}
|
|
822
|
+
renderAdditionalRowActions={(item, closeMenu) => (
|
|
823
|
+
<>
|
|
824
|
+
<button
|
|
825
|
+
type="button"
|
|
826
|
+
role="menuitem"
|
|
827
|
+
className="an-chat-history-row__menu-item"
|
|
828
|
+
onClick={() => {
|
|
829
|
+
closeMenu();
|
|
830
|
+
void writeClipboardText(item.id).then((copied) => {
|
|
831
|
+
toast[copied ? "success" : "error"](
|
|
832
|
+
copied
|
|
833
|
+
? "Session ID copied"
|
|
834
|
+
: "Could not copy the session ID",
|
|
835
|
+
);
|
|
836
|
+
});
|
|
837
|
+
}}
|
|
838
|
+
>
|
|
839
|
+
<IconCopy size={13} strokeWidth={1.8} />
|
|
840
|
+
<span>Copy session ID</span>
|
|
841
|
+
</button>
|
|
842
|
+
{chatFirstMode && !chatFirstEmbedded ? (
|
|
843
|
+
<button
|
|
844
|
+
type="button"
|
|
845
|
+
role="menuitem"
|
|
846
|
+
className="an-chat-history-row__menu-item"
|
|
847
|
+
onClick={() => {
|
|
848
|
+
closeMenu();
|
|
849
|
+
emitChatFirstSessionWatch({
|
|
850
|
+
sessionId: item.id,
|
|
851
|
+
title: item.titleText,
|
|
852
|
+
kind: "agent-chat",
|
|
853
|
+
sourceSessionId: displayedActiveThreadId ?? undefined,
|
|
854
|
+
});
|
|
855
|
+
}}
|
|
856
|
+
>
|
|
857
|
+
<IconEye size={13} strokeWidth={1.8} />
|
|
858
|
+
<span>Watch and message session</span>
|
|
859
|
+
</button>
|
|
860
|
+
) : null}
|
|
861
|
+
</>
|
|
862
|
+
)}
|
|
863
|
+
className="min-w-0"
|
|
864
|
+
/>
|
|
865
|
+
)}
|
|
467
866
|
</div>
|
|
468
867
|
);
|
|
469
868
|
}
|
|
@@ -471,17 +870,38 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
471
870
|
export function NavContent({
|
|
472
871
|
onNavigate,
|
|
473
872
|
extensions,
|
|
873
|
+
chatFirstMode = false,
|
|
874
|
+
chatFirstEmbedded = false,
|
|
474
875
|
collapsed = false,
|
|
475
876
|
collapsible = false,
|
|
476
877
|
onCollapsedChange,
|
|
878
|
+
chatFirstAppLayout,
|
|
879
|
+
onChatFirstAppLayoutChange,
|
|
880
|
+
chatFirstApps = [],
|
|
881
|
+
chatFirstAppsLoading = false,
|
|
882
|
+
chatFirstAppsError,
|
|
883
|
+
chatFirstActiveAppId,
|
|
884
|
+
onChatFirstAppOpen,
|
|
885
|
+
onChatFirstAppsRetry,
|
|
477
886
|
}: {
|
|
478
887
|
onNavigate?: () => void;
|
|
479
888
|
extensions?: DispatchExtensionConfig;
|
|
889
|
+
chatFirstMode?: boolean;
|
|
890
|
+
chatFirstEmbedded?: boolean;
|
|
480
891
|
collapsed?: boolean;
|
|
481
892
|
collapsible?: boolean;
|
|
482
893
|
onCollapsedChange?: (collapsed: boolean) => void;
|
|
894
|
+
chatFirstAppLayout?: ChatFirstAppLayoutPreference;
|
|
895
|
+
onChatFirstAppLayoutChange?: (layout: ChatFirstAppLayoutPreference) => void;
|
|
896
|
+
chatFirstApps?: readonly ChatFirstAppItem[];
|
|
897
|
+
chatFirstAppsLoading?: boolean;
|
|
898
|
+
chatFirstAppsError?: string | null;
|
|
899
|
+
chatFirstActiveAppId?: string;
|
|
900
|
+
onChatFirstAppOpen?: (app: ChatFirstAppItem) => void;
|
|
901
|
+
onChatFirstAppsRetry?: () => void;
|
|
483
902
|
}) {
|
|
484
903
|
const t = useT();
|
|
904
|
+
const chatFirstCopy = useMemo(() => createDispatchChatFirstCopy(t), [t]);
|
|
485
905
|
const location = useLocation();
|
|
486
906
|
const navigate = useNavigate();
|
|
487
907
|
const extensionNavItems = extensions?.navItems ?? EMPTY_NAV_ITEMS;
|
|
@@ -547,6 +967,62 @@ export function NavContent({
|
|
|
547
967
|
className={collapsed ? "size-8" : "min-w-0"}
|
|
548
968
|
/>
|
|
549
969
|
);
|
|
970
|
+
const chatFirstCreateAppTrigger = (
|
|
971
|
+
<CreateAppPopover
|
|
972
|
+
align="start"
|
|
973
|
+
trigger={
|
|
974
|
+
<button
|
|
975
|
+
type="button"
|
|
976
|
+
className="flex size-6 items-center justify-center rounded text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
|
977
|
+
aria-label={chatFirstCopy("createWorkspaceApp")}
|
|
978
|
+
title={chatFirstCopy("createWorkspaceApp")}
|
|
979
|
+
>
|
|
980
|
+
<IconPlus size={14} aria-hidden="true" />
|
|
981
|
+
</button>
|
|
982
|
+
}
|
|
983
|
+
/>
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
const chatFirstPrimaryNavigation = (
|
|
987
|
+
<ChatFirstPrimaryNavigation
|
|
988
|
+
copy={chatFirstCopy}
|
|
989
|
+
onOpenIntegrations={() => {
|
|
990
|
+
navigate(dispatchNavLinkTarget("/admin/integrations"));
|
|
991
|
+
onNavigate?.();
|
|
992
|
+
}}
|
|
993
|
+
onOpenScheduled={() => {
|
|
994
|
+
navigate(dispatchNavLinkTarget("/admin/automations"));
|
|
995
|
+
onNavigate?.();
|
|
996
|
+
}}
|
|
997
|
+
/>
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
const chatFirstAppsRail = (
|
|
1001
|
+
<ChatFirstAppsRail
|
|
1002
|
+
apps={chatFirstApps}
|
|
1003
|
+
activeAppId={chatFirstActiveAppId}
|
|
1004
|
+
collapsed={collapsed}
|
|
1005
|
+
loading={chatFirstAppsLoading}
|
|
1006
|
+
error={chatFirstAppsError}
|
|
1007
|
+
layout={chatFirstAppLayout}
|
|
1008
|
+
onLayoutChange={onChatFirstAppLayoutChange}
|
|
1009
|
+
onRetry={onChatFirstAppsRetry}
|
|
1010
|
+
onOpenApp={(app) => {
|
|
1011
|
+
onChatFirstAppOpen?.(app);
|
|
1012
|
+
onNavigate?.();
|
|
1013
|
+
}}
|
|
1014
|
+
createAppTrigger={chatFirstCreateAppTrigger}
|
|
1015
|
+
renderIcon={(app) => (
|
|
1016
|
+
<AppIcon
|
|
1017
|
+
id={app.id}
|
|
1018
|
+
name={app.name}
|
|
1019
|
+
size="sm"
|
|
1020
|
+
className="size-5 rounded-md"
|
|
1021
|
+
/>
|
|
1022
|
+
)}
|
|
1023
|
+
copy={chatFirstCopy}
|
|
1024
|
+
/>
|
|
1025
|
+
);
|
|
550
1026
|
|
|
551
1027
|
const renderNavItem = (item: DispatchNavItem) => {
|
|
552
1028
|
const Icon = item.icon;
|
|
@@ -633,7 +1109,11 @@ export function NavContent({
|
|
|
633
1109
|
</Link>
|
|
634
1110
|
)}
|
|
635
1111
|
{!collapsed && item.id === "chat" && itemMatchesLocalPath ? (
|
|
636
|
-
<DispatchChatsSection
|
|
1112
|
+
<DispatchChatsSection
|
|
1113
|
+
onNavigate={onNavigate}
|
|
1114
|
+
chatFirstMode={chatFirstMode}
|
|
1115
|
+
chatFirstEmbedded={chatFirstEmbedded}
|
|
1116
|
+
/>
|
|
637
1117
|
) : null}
|
|
638
1118
|
</li>
|
|
639
1119
|
);
|
|
@@ -688,7 +1168,33 @@ export function NavContent({
|
|
|
688
1168
|
</div>
|
|
689
1169
|
</div>
|
|
690
1170
|
|
|
691
|
-
|
|
1171
|
+
{chatFirstMode ? (
|
|
1172
|
+
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto">
|
|
1173
|
+
{!collapsed ? (
|
|
1174
|
+
<DispatchChatsSection
|
|
1175
|
+
onNavigate={onNavigate}
|
|
1176
|
+
showNewChat
|
|
1177
|
+
chatFirstMode
|
|
1178
|
+
prelude={
|
|
1179
|
+
<>
|
|
1180
|
+
<nav className="space-y-0.5 px-2 py-2">
|
|
1181
|
+
{chatFirstPrimaryNavigation}
|
|
1182
|
+
</nav>
|
|
1183
|
+
{chatFirstAppsRail}
|
|
1184
|
+
</>
|
|
1185
|
+
}
|
|
1186
|
+
/>
|
|
1187
|
+
) : (
|
|
1188
|
+
chatFirstAppsRail
|
|
1189
|
+
)}
|
|
1190
|
+
</div>
|
|
1191
|
+
) : null}
|
|
1192
|
+
<div
|
|
1193
|
+
className={cn(
|
|
1194
|
+
"flex min-h-0 flex-1 flex-col overflow-y-auto",
|
|
1195
|
+
chatFirstMode && "hidden",
|
|
1196
|
+
)}
|
|
1197
|
+
>
|
|
692
1198
|
<nav className={cn("py-2", collapsed ? "px-1.5" : "px-2")}>
|
|
693
1199
|
<ul
|
|
694
1200
|
className={cn(
|
|
@@ -727,6 +1233,16 @@ export function NavContent({
|
|
|
727
1233
|
collapse={collapseButton}
|
|
728
1234
|
/>
|
|
729
1235
|
</div>
|
|
1236
|
+
{chatFirstMode && collapsed ? (
|
|
1237
|
+
<div className="mt-auto shrink-0">
|
|
1238
|
+
<SidebarFooterActions
|
|
1239
|
+
collapsed
|
|
1240
|
+
feedback={feedbackButton}
|
|
1241
|
+
search={searchButton}
|
|
1242
|
+
collapse={collapseButton}
|
|
1243
|
+
/>
|
|
1244
|
+
</div>
|
|
1245
|
+
) : null}
|
|
730
1246
|
</>
|
|
731
1247
|
);
|
|
732
1248
|
}
|
|
@@ -755,7 +1271,275 @@ export function Layout({
|
|
|
755
1271
|
const localPathname = localDispatchPath(location.pathname);
|
|
756
1272
|
const isChatRoute =
|
|
757
1273
|
localPathname === "/chat" || localPathname.startsWith("/chat/");
|
|
1274
|
+
const chatFirstSurfaceScope = threadIdFromPath(localPathname) ?? "new";
|
|
758
1275
|
const isWorkspaceAppHostRoute = localPathname.startsWith("/apps/");
|
|
1276
|
+
const [chatFirstPreference, setChatFirstPreference] = useState(() =>
|
|
1277
|
+
readChatFirstMode(),
|
|
1278
|
+
);
|
|
1279
|
+
const chatFirstEmbedded =
|
|
1280
|
+
typeof window !== "undefined" &&
|
|
1281
|
+
new URLSearchParams(window.location.search).get("chatFirst") === "1";
|
|
1282
|
+
const chatFirstMode =
|
|
1283
|
+
extensions?.chatFirst === true || chatFirstPreference || chatFirstEmbedded;
|
|
1284
|
+
const [chatFirstAppLayout, setChatFirstAppLayout] =
|
|
1285
|
+
useState<ChatFirstAppLayoutPreference>(() => readChatFirstAppLayout());
|
|
1286
|
+
const chatFirstAppLayoutHydratedRef = useRef(false);
|
|
1287
|
+
const chatFirstAppsQuery = useActionQuery<WorkspaceAppSummary[]>(
|
|
1288
|
+
"list-workspace-apps",
|
|
1289
|
+
{ includeAgentCards: false },
|
|
1290
|
+
{ enabled: chatFirstMode && isChatRoute },
|
|
1291
|
+
);
|
|
1292
|
+
const chatFirstWorkspaceApps = useMemo(
|
|
1293
|
+
() => mergeChatFirstWorkspaceApps(chatFirstAppsQuery.data),
|
|
1294
|
+
[chatFirstAppsQuery.data],
|
|
1295
|
+
);
|
|
1296
|
+
const chatFirstAppRegistrations = useMemo<ChatFirstAppRegistration[]>(
|
|
1297
|
+
() =>
|
|
1298
|
+
chatFirstWorkspaceApps.map((app) => ({
|
|
1299
|
+
id: app.id,
|
|
1300
|
+
name: app.name,
|
|
1301
|
+
path: app.path,
|
|
1302
|
+
url: app.url,
|
|
1303
|
+
enabled: app.status !== "pending" && app.archived !== true,
|
|
1304
|
+
})),
|
|
1305
|
+
[chatFirstWorkspaceApps],
|
|
1306
|
+
);
|
|
1307
|
+
const chatFirstAppItems = useMemo<ChatFirstAppItem[]>(
|
|
1308
|
+
() =>
|
|
1309
|
+
chatFirstWorkspaceApps.map((app) => ({
|
|
1310
|
+
id: app.id,
|
|
1311
|
+
name: app.name,
|
|
1312
|
+
})),
|
|
1313
|
+
[chatFirstWorkspaceApps],
|
|
1314
|
+
);
|
|
1315
|
+
const chatFirstCopy = useMemo(() => createDispatchChatFirstCopy(t), [t]);
|
|
1316
|
+
const createChatFirstEmbedSession = useActionMutation<
|
|
1317
|
+
ChatFirstEmbedSessionResult,
|
|
1318
|
+
ChatFirstEmbedSessionInput
|
|
1319
|
+
>("create_embed_session", { skipActionQueryInvalidation: true });
|
|
1320
|
+
const [chatFirstPane, setChatFirstPane] =
|
|
1321
|
+
useState<DispatchChatFirstPane | null>(null);
|
|
1322
|
+
const [chatFirstEmbedUrl, setChatFirstEmbedUrl] = useState<string | null>(
|
|
1323
|
+
null,
|
|
1324
|
+
);
|
|
1325
|
+
const [chatFirstEmbedError, setChatFirstEmbedError] = useState<string | null>(
|
|
1326
|
+
null,
|
|
1327
|
+
);
|
|
1328
|
+
const [chatFirstEmbedAttempt, setChatFirstEmbedAttempt] = useState(0);
|
|
1329
|
+
const [chatFirstNotice, setChatFirstNotice] = useState<string | null>(null);
|
|
1330
|
+
const chatFirstSessionWatch = useChatFirstSessionWatch();
|
|
1331
|
+
const chatFirstSurfaceTabs = useChatFirstSurfaceTabs(chatFirstSurfaceScope);
|
|
1332
|
+
const chatFirstSurfaceTabsStore = getChatFirstSurfaceTabsStore(
|
|
1333
|
+
chatFirstSurfaceScope,
|
|
1334
|
+
);
|
|
1335
|
+
const chatFirstAgentsQuery = useActionQuery<SearchAgentThreadsResult>(
|
|
1336
|
+
"search-agent-threads",
|
|
1337
|
+
{ sourceId: "current", limit: 12 },
|
|
1338
|
+
{
|
|
1339
|
+
enabled:
|
|
1340
|
+
chatFirstMode &&
|
|
1341
|
+
isChatRoute &&
|
|
1342
|
+
chatFirstSurfaceTabs.activeTabId?.startsWith("agents:") === true,
|
|
1343
|
+
},
|
|
1344
|
+
);
|
|
1345
|
+
const chatFirstSurfacePanel = useChatFirstSurfacePanel(chatFirstSurfaceScope);
|
|
1346
|
+
const { setOpen: setChatFirstSurfacePanelOpen } = chatFirstSurfacePanel;
|
|
1347
|
+
const chatFirstSurfaceResize = useChatFirstSurfaceResize(
|
|
1348
|
+
chatFirstSurfaceScope,
|
|
1349
|
+
);
|
|
1350
|
+
const chatFirstPaneHydratedRef = useRef(false);
|
|
1351
|
+
const pendingChatFirstOpenAppRef = useRef<ChatFirstOpenAppDetail | null>(
|
|
1352
|
+
null,
|
|
1353
|
+
);
|
|
1354
|
+
const previousChatFirstSurfaceScopeRef = useRef(chatFirstSurfaceScope);
|
|
1355
|
+
const previousChatFirstSurfaceTabCountRef = useRef<number | null>(null);
|
|
1356
|
+
const activeChatFirstSurfaceTab = useMemo(
|
|
1357
|
+
() =>
|
|
1358
|
+
chatFirstSurfaceTabs.tabs.find(
|
|
1359
|
+
(tab) => tab.id === chatFirstSurfaceTabs.activeTabId,
|
|
1360
|
+
) ?? null,
|
|
1361
|
+
[chatFirstSurfaceTabs],
|
|
1362
|
+
);
|
|
1363
|
+
const activeChatFirstApp = useMemo(
|
|
1364
|
+
() =>
|
|
1365
|
+
activeChatFirstSurfaceTab?.kind === "app" &&
|
|
1366
|
+
activeChatFirstSurfaceTab.appId
|
|
1367
|
+
? (chatFirstWorkspaceApps.find(
|
|
1368
|
+
(app) => app.id === activeChatFirstSurfaceTab.appId,
|
|
1369
|
+
) ?? null)
|
|
1370
|
+
: null,
|
|
1371
|
+
[activeChatFirstSurfaceTab, chatFirstWorkspaceApps],
|
|
1372
|
+
);
|
|
1373
|
+
const chatFirstAgentActivities = useMemo<ChatFirstAgentActivity[]>(
|
|
1374
|
+
() =>
|
|
1375
|
+
(chatFirstAgentsQuery.data?.threads ?? []).map((thread) => ({
|
|
1376
|
+
sessionId: thread.id,
|
|
1377
|
+
title:
|
|
1378
|
+
thread.title || chatFirstCopy("agentsTitle", { name: thread.id }),
|
|
1379
|
+
subtitle: thread.snippet || thread.preview || chatFirstCopy("session"),
|
|
1380
|
+
status: "recent",
|
|
1381
|
+
...(thread.updatedAt ? { updatedAt: thread.updatedAt } : {}),
|
|
1382
|
+
})),
|
|
1383
|
+
[chatFirstAgentsQuery.data, chatFirstCopy],
|
|
1384
|
+
);
|
|
1385
|
+
const chatFirstEmbedPath =
|
|
1386
|
+
activeChatFirstSurfaceTab?.kind === "app"
|
|
1387
|
+
? (activeChatFirstSurfaceTab.path ??
|
|
1388
|
+
chatFirstPane?.path ??
|
|
1389
|
+
activeChatFirstApp?.path)
|
|
1390
|
+
: null;
|
|
1391
|
+
useEffect(() => {
|
|
1392
|
+
if (!chatFirstMode || !isChatRoute || !activeChatFirstApp) {
|
|
1393
|
+
setChatFirstEmbedUrl(null);
|
|
1394
|
+
setChatFirstEmbedError(null);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (!chatFirstEmbedPath?.startsWith("/")) {
|
|
1398
|
+
setChatFirstEmbedUrl(null);
|
|
1399
|
+
setChatFirstEmbedError(chatFirstCopy("appUnavailable"));
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
let cancelled = false;
|
|
1403
|
+
setChatFirstEmbedUrl(null);
|
|
1404
|
+
setChatFirstEmbedError(null);
|
|
1405
|
+
void createChatFirstEmbedSession
|
|
1406
|
+
.mutateAsync(
|
|
1407
|
+
buildChatFirstEmbedSessionInput(
|
|
1408
|
+
activeChatFirstApp.id,
|
|
1409
|
+
chatFirstEmbedPath,
|
|
1410
|
+
),
|
|
1411
|
+
)
|
|
1412
|
+
.then((result) => {
|
|
1413
|
+
if (!cancelled) setChatFirstEmbedUrl(result.startUrl);
|
|
1414
|
+
})
|
|
1415
|
+
.catch((cause: unknown) => {
|
|
1416
|
+
if (!cancelled) {
|
|
1417
|
+
setChatFirstEmbedError(
|
|
1418
|
+
cause instanceof Error
|
|
1419
|
+
? cause.message
|
|
1420
|
+
: chatFirstCopy("appUnavailable"),
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
return () => {
|
|
1425
|
+
cancelled = true;
|
|
1426
|
+
};
|
|
1427
|
+
}, [
|
|
1428
|
+
activeChatFirstApp,
|
|
1429
|
+
chatFirstCopy,
|
|
1430
|
+
chatFirstEmbedAttempt,
|
|
1431
|
+
chatFirstEmbedPath,
|
|
1432
|
+
chatFirstMode,
|
|
1433
|
+
createChatFirstEmbedSession.mutateAsync,
|
|
1434
|
+
isChatRoute,
|
|
1435
|
+
]);
|
|
1436
|
+
const persistChatFirstPane = useCallback(
|
|
1437
|
+
(pane: DispatchChatFirstPane | null) => {
|
|
1438
|
+
setChatFirstPane(pane);
|
|
1439
|
+
void writeClientAppState(CHAT_FIRST_PANE_STATE_KEY, pane).catch(() => {
|
|
1440
|
+
setChatFirstNotice(
|
|
1441
|
+
"The app pane opened locally, but its focus could not be synced.",
|
|
1442
|
+
);
|
|
1443
|
+
});
|
|
1444
|
+
},
|
|
1445
|
+
[],
|
|
1446
|
+
);
|
|
1447
|
+
const openChatFirstPane = useCallback(
|
|
1448
|
+
(pane: DispatchChatFirstPane) => {
|
|
1449
|
+
setChatFirstNotice(null);
|
|
1450
|
+
closeChatFirstSessionWatch();
|
|
1451
|
+
const app = chatFirstAppRegistrations.find(
|
|
1452
|
+
(candidate) => candidate.id === pane.appId,
|
|
1453
|
+
);
|
|
1454
|
+
chatFirstSurfaceTabsStore.open({
|
|
1455
|
+
id: chatFirstSurfaceTabId(
|
|
1456
|
+
"app",
|
|
1457
|
+
`${pane.appId}:${pane.path ?? pane.view ?? "/"}`,
|
|
1458
|
+
),
|
|
1459
|
+
kind: "app",
|
|
1460
|
+
title: app?.name ?? pane.appId,
|
|
1461
|
+
appId: pane.appId,
|
|
1462
|
+
...(pane.path ? { path: pane.path } : {}),
|
|
1463
|
+
...(pane.view ? { view: pane.view } : {}),
|
|
1464
|
+
});
|
|
1465
|
+
persistChatFirstPane(pane);
|
|
1466
|
+
},
|
|
1467
|
+
[
|
|
1468
|
+
chatFirstAppRegistrations,
|
|
1469
|
+
chatFirstSurfaceTabsStore,
|
|
1470
|
+
persistChatFirstPane,
|
|
1471
|
+
],
|
|
1472
|
+
);
|
|
1473
|
+
const openChatFirstSurface = useCallback(
|
|
1474
|
+
(kind: ChatFirstSurfaceKind) => {
|
|
1475
|
+
if (kind !== "agents") return;
|
|
1476
|
+
persistChatFirstPane(null);
|
|
1477
|
+
closeChatFirstSessionWatch();
|
|
1478
|
+
chatFirstSurfaceTabsStore.open({
|
|
1479
|
+
id: chatFirstSurfaceTabId("agents", "activity"),
|
|
1480
|
+
kind: "agents",
|
|
1481
|
+
title: "Agents",
|
|
1482
|
+
});
|
|
1483
|
+
},
|
|
1484
|
+
[chatFirstSurfaceTabsStore, persistChatFirstPane],
|
|
1485
|
+
);
|
|
1486
|
+
const resolveChatFirstOpenBrowser = useCallback(
|
|
1487
|
+
(detail: ChatFirstOpenBrowserDetail) => {
|
|
1488
|
+
const resolution = resolveChatFirstBrowserTarget(detail);
|
|
1489
|
+
if (resolution.status === "unresolved") {
|
|
1490
|
+
setChatFirstNotice(
|
|
1491
|
+
chatFirstBrowserResolutionMessage(resolution.reason),
|
|
1492
|
+
);
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
persistChatFirstPane(null);
|
|
1496
|
+
closeChatFirstSessionWatch();
|
|
1497
|
+
setChatFirstNotice(null);
|
|
1498
|
+
chatFirstSurfaceTabsStore.open({
|
|
1499
|
+
id: chatFirstSurfaceTabId("browser", resolution.target.url),
|
|
1500
|
+
kind: "browser",
|
|
1501
|
+
title: resolution.target.title ?? "Browser",
|
|
1502
|
+
url: resolution.target.url,
|
|
1503
|
+
});
|
|
1504
|
+
},
|
|
1505
|
+
[chatFirstSurfaceTabsStore, persistChatFirstPane],
|
|
1506
|
+
);
|
|
1507
|
+
const resolveChatFirstOpenApp = useCallback(
|
|
1508
|
+
(detail: ChatFirstOpenAppDetail) => {
|
|
1509
|
+
if (chatFirstAppsQuery.isLoading) {
|
|
1510
|
+
pendingChatFirstOpenAppRef.current = detail;
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
if (chatFirstAppsQuery.isError) {
|
|
1514
|
+
pendingChatFirstOpenAppRef.current = null;
|
|
1515
|
+
setChatFirstNotice(
|
|
1516
|
+
"Workspace apps could not be loaded, so the requested app was not opened.",
|
|
1517
|
+
);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
const resolution = resolveChatFirstAppTarget(
|
|
1522
|
+
detail,
|
|
1523
|
+
chatFirstAppRegistrations,
|
|
1524
|
+
{
|
|
1525
|
+
currentOrigin:
|
|
1526
|
+
typeof window === "undefined" ? undefined : window.location.origin,
|
|
1527
|
+
},
|
|
1528
|
+
);
|
|
1529
|
+
pendingChatFirstOpenAppRef.current = null;
|
|
1530
|
+
if (resolution.status === "unresolved") {
|
|
1531
|
+
setChatFirstNotice(chatFirstResolutionMessage(resolution.reason));
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
openChatFirstPane(resolution.target);
|
|
1535
|
+
},
|
|
1536
|
+
[
|
|
1537
|
+
chatFirstAppRegistrations,
|
|
1538
|
+
chatFirstAppsQuery.isError,
|
|
1539
|
+
chatFirstAppsQuery.isLoading,
|
|
1540
|
+
openChatFirstPane,
|
|
1541
|
+
],
|
|
1542
|
+
);
|
|
759
1543
|
const sidebarBeforeAppRef = useRef<boolean | null>(null);
|
|
760
1544
|
const sidebarAutoCollapsedRef = useRef(false);
|
|
761
1545
|
const chatHomeHandoffActive = useAgentChatHomeHandoff({
|
|
@@ -771,6 +1555,227 @@ export function Layout({
|
|
|
771
1555
|
};
|
|
772
1556
|
useAgentChatHomeHandoffLinks(chatHandoffLinkOptions);
|
|
773
1557
|
|
|
1558
|
+
useEffect(() => {
|
|
1559
|
+
const handleModeChange = (event: Event) => {
|
|
1560
|
+
const enabled = (event as CustomEvent<{ enabled?: unknown }>).detail
|
|
1561
|
+
?.enabled;
|
|
1562
|
+
setChatFirstPreference(enabled === true);
|
|
1563
|
+
};
|
|
1564
|
+
window.addEventListener(CHAT_FIRST_MODE_CHANGED_EVENT, handleModeChange);
|
|
1565
|
+
return () =>
|
|
1566
|
+
window.removeEventListener(
|
|
1567
|
+
CHAT_FIRST_MODE_CHANGED_EVENT,
|
|
1568
|
+
handleModeChange,
|
|
1569
|
+
);
|
|
1570
|
+
}, []);
|
|
1571
|
+
|
|
1572
|
+
useEffect(() => {
|
|
1573
|
+
if (
|
|
1574
|
+
!chatFirstMode ||
|
|
1575
|
+
!isChatRoute ||
|
|
1576
|
+
chatFirstAppLayoutHydratedRef.current
|
|
1577
|
+
) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
chatFirstAppLayoutHydratedRef.current = true;
|
|
1581
|
+
void readClientAppState<unknown>("chat-first-app-layout")
|
|
1582
|
+
.then((value) => {
|
|
1583
|
+
if (!value || typeof value !== "object") return;
|
|
1584
|
+
const candidate = value as Partial<ChatFirstAppLayoutPreference>;
|
|
1585
|
+
const ids = (input: unknown) =>
|
|
1586
|
+
Array.isArray(input)
|
|
1587
|
+
? input.filter(
|
|
1588
|
+
(id): id is string =>
|
|
1589
|
+
typeof id === "string" && id.trim().length > 0,
|
|
1590
|
+
)
|
|
1591
|
+
: [];
|
|
1592
|
+
setChatFirstAppLayout({
|
|
1593
|
+
pinnedIds: [...new Set(ids(candidate.pinnedIds))],
|
|
1594
|
+
orderedIds: [...new Set(ids(candidate.orderedIds))],
|
|
1595
|
+
});
|
|
1596
|
+
})
|
|
1597
|
+
.catch(() => {
|
|
1598
|
+
// Device-local layout remains the fallback when workspace state is unavailable.
|
|
1599
|
+
});
|
|
1600
|
+
}, [chatFirstMode, isChatRoute]);
|
|
1601
|
+
|
|
1602
|
+
const persistChatFirstAppLayout = useCallback(
|
|
1603
|
+
(layout: ChatFirstAppLayoutPreference) => {
|
|
1604
|
+
setChatFirstAppLayout(layout);
|
|
1605
|
+
void writeClientAppState("chat-first-app-layout", layout).catch(() => {
|
|
1606
|
+
setChatFirstNotice(
|
|
1607
|
+
"App order changed locally, but workspace state could not be synced.",
|
|
1608
|
+
);
|
|
1609
|
+
});
|
|
1610
|
+
},
|
|
1611
|
+
[],
|
|
1612
|
+
);
|
|
1613
|
+
|
|
1614
|
+
useEffect(() => {
|
|
1615
|
+
if (!chatFirstMode || !isChatRoute) {
|
|
1616
|
+
setChatFirstPane(null);
|
|
1617
|
+
closeChatFirstSessionWatch();
|
|
1618
|
+
chatFirstSurfaceTabsStore.closeAll();
|
|
1619
|
+
setChatFirstNotice(null);
|
|
1620
|
+
pendingChatFirstOpenAppRef.current = null;
|
|
1621
|
+
chatFirstPaneHydratedRef.current = false;
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const unsubscribeApp = subscribeChatFirstOpenApp(resolveChatFirstOpenApp);
|
|
1625
|
+
const unsubscribeBrowser = subscribeChatFirstOpenBrowser(
|
|
1626
|
+
resolveChatFirstOpenBrowser,
|
|
1627
|
+
);
|
|
1628
|
+
return () => {
|
|
1629
|
+
unsubscribeApp();
|
|
1630
|
+
unsubscribeBrowser();
|
|
1631
|
+
};
|
|
1632
|
+
}, [
|
|
1633
|
+
chatFirstMode,
|
|
1634
|
+
isChatRoute,
|
|
1635
|
+
chatFirstSurfaceTabsStore,
|
|
1636
|
+
resolveChatFirstOpenApp,
|
|
1637
|
+
resolveChatFirstOpenBrowser,
|
|
1638
|
+
]);
|
|
1639
|
+
|
|
1640
|
+
useEffect(() => {
|
|
1641
|
+
if (
|
|
1642
|
+
!chatFirstMode ||
|
|
1643
|
+
!isChatRoute ||
|
|
1644
|
+
chatFirstPaneHydratedRef.current ||
|
|
1645
|
+
chatFirstAppsQuery.isLoading ||
|
|
1646
|
+
chatFirstAppsQuery.isError
|
|
1647
|
+
) {
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
chatFirstPaneHydratedRef.current = true;
|
|
1651
|
+
if (chatFirstPane) return;
|
|
1652
|
+
let active = true;
|
|
1653
|
+
void readClientAppState<unknown>(CHAT_FIRST_PANE_STATE_KEY)
|
|
1654
|
+
.then((value) => {
|
|
1655
|
+
if (!active) return;
|
|
1656
|
+
const persisted = persistedChatFirstPane(value);
|
|
1657
|
+
if (!persisted) return;
|
|
1658
|
+
const resolution = resolveChatFirstAppTarget(
|
|
1659
|
+
{
|
|
1660
|
+
app: persisted.appId,
|
|
1661
|
+
path: persisted.path,
|
|
1662
|
+
view: persisted.view,
|
|
1663
|
+
},
|
|
1664
|
+
chatFirstAppRegistrations,
|
|
1665
|
+
{
|
|
1666
|
+
currentOrigin:
|
|
1667
|
+
typeof window === "undefined"
|
|
1668
|
+
? undefined
|
|
1669
|
+
: window.location.origin,
|
|
1670
|
+
},
|
|
1671
|
+
);
|
|
1672
|
+
if (resolution.status === "ready") openChatFirstPane(resolution.target);
|
|
1673
|
+
})
|
|
1674
|
+
.catch(() => {
|
|
1675
|
+
if (active) {
|
|
1676
|
+
setChatFirstNotice(
|
|
1677
|
+
"The last app pane could not be restored from workspace state.",
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
return () => {
|
|
1682
|
+
active = false;
|
|
1683
|
+
};
|
|
1684
|
+
}, [
|
|
1685
|
+
chatFirstAppsQuery.isError,
|
|
1686
|
+
chatFirstAppsQuery.isLoading,
|
|
1687
|
+
chatFirstAppRegistrations,
|
|
1688
|
+
chatFirstMode,
|
|
1689
|
+
chatFirstPane,
|
|
1690
|
+
isChatRoute,
|
|
1691
|
+
openChatFirstPane,
|
|
1692
|
+
]);
|
|
1693
|
+
|
|
1694
|
+
useEffect(() => {
|
|
1695
|
+
if (previousChatFirstSurfaceScopeRef.current === chatFirstSurfaceScope) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
previousChatFirstSurfaceScopeRef.current = chatFirstSurfaceScope;
|
|
1699
|
+
persistChatFirstPane(null);
|
|
1700
|
+
setChatFirstEmbedUrl(null);
|
|
1701
|
+
setChatFirstEmbedError(null);
|
|
1702
|
+
setChatFirstNotice(null);
|
|
1703
|
+
pendingChatFirstOpenAppRef.current = null;
|
|
1704
|
+
chatFirstPaneHydratedRef.current = true;
|
|
1705
|
+
chatFirstSurfaceTabsStore.closeAll();
|
|
1706
|
+
setChatFirstSurfacePanelOpen(false);
|
|
1707
|
+
closeChatFirstSessionWatch();
|
|
1708
|
+
}, [
|
|
1709
|
+
chatFirstSurfaceScope,
|
|
1710
|
+
chatFirstSurfaceTabsStore,
|
|
1711
|
+
persistChatFirstPane,
|
|
1712
|
+
setChatFirstSurfacePanelOpen,
|
|
1713
|
+
]);
|
|
1714
|
+
|
|
1715
|
+
useEffect(() => {
|
|
1716
|
+
const tabCount = chatFirstSurfaceTabs.tabs.length;
|
|
1717
|
+
const previousTabCount = previousChatFirstSurfaceTabCountRef.current;
|
|
1718
|
+
if (!chatFirstMode || !isChatRoute) {
|
|
1719
|
+
setChatFirstSurfacePanelOpen(false);
|
|
1720
|
+
} else if (
|
|
1721
|
+
tabCount > 0 &&
|
|
1722
|
+
(previousTabCount === null || previousTabCount === 0)
|
|
1723
|
+
) {
|
|
1724
|
+
setChatFirstSurfacePanelOpen(true);
|
|
1725
|
+
} else if (
|
|
1726
|
+
tabCount === 0 &&
|
|
1727
|
+
previousTabCount !== null &&
|
|
1728
|
+
previousTabCount > 0
|
|
1729
|
+
) {
|
|
1730
|
+
setChatFirstSurfacePanelOpen(false);
|
|
1731
|
+
}
|
|
1732
|
+
previousChatFirstSurfaceTabCountRef.current = tabCount;
|
|
1733
|
+
}, [
|
|
1734
|
+
chatFirstMode,
|
|
1735
|
+
setChatFirstSurfacePanelOpen,
|
|
1736
|
+
chatFirstSurfaceTabs.tabs.length,
|
|
1737
|
+
isChatRoute,
|
|
1738
|
+
]);
|
|
1739
|
+
|
|
1740
|
+
useEffect(() => {
|
|
1741
|
+
const activeTab = activeChatFirstSurfaceTab;
|
|
1742
|
+
if (
|
|
1743
|
+
activeTab?.kind === "side-chat" &&
|
|
1744
|
+
activeTab.session &&
|
|
1745
|
+
!chatFirstSessionWatch.target
|
|
1746
|
+
) {
|
|
1747
|
+
emitChatFirstSessionWatch(activeTab.session);
|
|
1748
|
+
}
|
|
1749
|
+
}, [activeChatFirstSurfaceTab, chatFirstSessionWatch.target]);
|
|
1750
|
+
|
|
1751
|
+
useEffect(() => {
|
|
1752
|
+
const pending = pendingChatFirstOpenAppRef.current;
|
|
1753
|
+
if (!pending || chatFirstAppsQuery.isLoading) return;
|
|
1754
|
+
resolveChatFirstOpenApp(pending);
|
|
1755
|
+
}, [
|
|
1756
|
+
chatFirstAppsQuery.data,
|
|
1757
|
+
chatFirstAppsQuery.isError,
|
|
1758
|
+
chatFirstAppsQuery.isLoading,
|
|
1759
|
+
resolveChatFirstOpenApp,
|
|
1760
|
+
]);
|
|
1761
|
+
|
|
1762
|
+
useEffect(() => {
|
|
1763
|
+
const target = chatFirstSessionWatch.target;
|
|
1764
|
+
if (!target || !chatFirstMode || !isChatRoute) return;
|
|
1765
|
+
setChatFirstPane(null);
|
|
1766
|
+
chatFirstSurfaceTabsStore.open({
|
|
1767
|
+
id: chatFirstSurfaceTabId("side-chat", target.sessionId),
|
|
1768
|
+
kind: "side-chat",
|
|
1769
|
+
title: target.title ? `Watch · ${target.title}` : "Watched session",
|
|
1770
|
+
session: target,
|
|
1771
|
+
});
|
|
1772
|
+
}, [
|
|
1773
|
+
chatFirstMode,
|
|
1774
|
+
chatFirstSessionWatch.target,
|
|
1775
|
+
chatFirstSurfaceTabsStore,
|
|
1776
|
+
isChatRoute,
|
|
1777
|
+
]);
|
|
1778
|
+
|
|
774
1779
|
useEffect(() => {
|
|
775
1780
|
if (isWorkspaceAppHostRoute) {
|
|
776
1781
|
if (!sidebarAutoCollapsedRef.current) {
|
|
@@ -805,10 +1810,222 @@ export function Layout({
|
|
|
805
1810
|
}
|
|
806
1811
|
}, [isWorkspaceAppHostRoute, sidebarCollapsed]);
|
|
807
1812
|
|
|
1813
|
+
const activateChatFirstSurfaceTab = useCallback(
|
|
1814
|
+
(tab: (typeof chatFirstSurfaceTabs.tabs)[number]) => {
|
|
1815
|
+
chatFirstSurfaceTabsStore.activate(tab.id);
|
|
1816
|
+
if (tab.kind === "app" && tab.appId) {
|
|
1817
|
+
closeChatFirstSessionWatch();
|
|
1818
|
+
persistChatFirstPane({
|
|
1819
|
+
appId: tab.appId,
|
|
1820
|
+
...(tab.path ? { path: tab.path } : {}),
|
|
1821
|
+
...(tab.view ? { view: tab.view } : {}),
|
|
1822
|
+
});
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
if (tab.kind === "browser" && tab.url) {
|
|
1826
|
+
persistChatFirstPane(null);
|
|
1827
|
+
closeChatFirstSessionWatch();
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
if (tab.kind === "side-chat" && tab.session) {
|
|
1831
|
+
persistChatFirstPane(null);
|
|
1832
|
+
emitChatFirstSessionWatch(tab.session);
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
if (tab.kind === "agents") {
|
|
1836
|
+
persistChatFirstPane(null);
|
|
1837
|
+
closeChatFirstSessionWatch();
|
|
1838
|
+
}
|
|
1839
|
+
},
|
|
1840
|
+
[
|
|
1841
|
+
chatFirstSurfaceTabs.tabs,
|
|
1842
|
+
chatFirstSurfaceTabsStore,
|
|
1843
|
+
persistChatFirstPane,
|
|
1844
|
+
],
|
|
1845
|
+
);
|
|
1846
|
+
|
|
1847
|
+
const closeChatFirstSurfaceTab = useCallback(
|
|
1848
|
+
(tab: (typeof chatFirstSurfaceTabs.tabs)[number]) => {
|
|
1849
|
+
const isActive = chatFirstSurfaceTabs.activeTabId === tab.id;
|
|
1850
|
+
if (tab.kind === "app" && isActive) persistChatFirstPane(null);
|
|
1851
|
+
if (tab.kind === "side-chat" && isActive) {
|
|
1852
|
+
closeChatFirstSessionWatch();
|
|
1853
|
+
}
|
|
1854
|
+
chatFirstSurfaceTabsStore.close(tab.id);
|
|
1855
|
+
},
|
|
1856
|
+
[chatFirstSurfaceTabs, chatFirstSurfaceTabsStore, persistChatFirstPane],
|
|
1857
|
+
);
|
|
1858
|
+
|
|
1859
|
+
const closeAllChatFirstSurfaceTabs = useCallback(() => {
|
|
1860
|
+
persistChatFirstPane(null);
|
|
1861
|
+
closeChatFirstSessionWatch();
|
|
1862
|
+
chatFirstSurfaceTabsStore.closeAll();
|
|
1863
|
+
}, [chatFirstSurfaceTabsStore, persistChatFirstPane]);
|
|
1864
|
+
|
|
1865
|
+
const renderChatFirstWatchChat = useCallback(
|
|
1866
|
+
(target: ChatFirstSessionReference) => (
|
|
1867
|
+
<AgentChatSurface
|
|
1868
|
+
mode="page"
|
|
1869
|
+
className="h-full min-h-0 w-full"
|
|
1870
|
+
defaultMode="chat"
|
|
1871
|
+
storageKey={`dispatch-session-watch:${target.sessionId}`}
|
|
1872
|
+
restoreActiveThread={false}
|
|
1873
|
+
threadUrlSync={{
|
|
1874
|
+
routeThreadId: target.sessionId,
|
|
1875
|
+
getPath: chatThreadPath,
|
|
1876
|
+
navigate: () => undefined,
|
|
1877
|
+
}}
|
|
1878
|
+
showHeader={false}
|
|
1879
|
+
showTabBar={false}
|
|
1880
|
+
showPageNewChatButton={false}
|
|
1881
|
+
chatOnly
|
|
1882
|
+
dynamicSuggestions={false}
|
|
1883
|
+
suggestions={[]}
|
|
1884
|
+
emptyStateDisplay="hidden"
|
|
1885
|
+
composerPlaceholder={t(
|
|
1886
|
+
"dispatch.pages.chatFirstSessionMessagePlaceholder",
|
|
1887
|
+
)}
|
|
1888
|
+
/>
|
|
1889
|
+
),
|
|
1890
|
+
[t],
|
|
1891
|
+
);
|
|
1892
|
+
|
|
1893
|
+
const renderChatFirstSurfaceTab = useCallback(
|
|
1894
|
+
(tab: ChatFirstSurfaceTab) => {
|
|
1895
|
+
if (tab.kind === "app") {
|
|
1896
|
+
if (tab.id !== chatFirstSurfaceTabs.activeTabId) return null;
|
|
1897
|
+
const app = tab.appId
|
|
1898
|
+
? (chatFirstWorkspaceApps.find(
|
|
1899
|
+
(candidate) => candidate.id === tab.appId,
|
|
1900
|
+
) ?? null)
|
|
1901
|
+
: null;
|
|
1902
|
+
return (
|
|
1903
|
+
<ChatFirstAppPane
|
|
1904
|
+
app={app}
|
|
1905
|
+
status={
|
|
1906
|
+
chatFirstEmbedError
|
|
1907
|
+
? "error"
|
|
1908
|
+
: chatFirstEmbedUrl
|
|
1909
|
+
? "ready"
|
|
1910
|
+
: chatFirstAppsQuery.isLoading
|
|
1911
|
+
? "loading"
|
|
1912
|
+
: app
|
|
1913
|
+
? "loading"
|
|
1914
|
+
: "unresolved"
|
|
1915
|
+
}
|
|
1916
|
+
embedUrl={chatFirstEmbedUrl}
|
|
1917
|
+
errorMessage={chatFirstEmbedError}
|
|
1918
|
+
onRetry={() => setChatFirstEmbedAttempt((value) => value + 1)}
|
|
1919
|
+
renderEmbed={({ url, title }: ChatFirstEmbedTarget) => (
|
|
1920
|
+
<iframe
|
|
1921
|
+
data-dispatch-chat-first-app-frame
|
|
1922
|
+
src={url}
|
|
1923
|
+
title={title ?? chatFirstCopy("appUnavailable")}
|
|
1924
|
+
referrerPolicy="no-referrer"
|
|
1925
|
+
allow="clipboard-read; clipboard-write"
|
|
1926
|
+
className="h-full w-full border-0 bg-background"
|
|
1927
|
+
/>
|
|
1928
|
+
)}
|
|
1929
|
+
copy={chatFirstCopy}
|
|
1930
|
+
/>
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
if (tab.kind === "browser" && tab.url) {
|
|
1934
|
+
return (
|
|
1935
|
+
<ChatFirstBrowserPane
|
|
1936
|
+
url={tab.url}
|
|
1937
|
+
title={tab.title}
|
|
1938
|
+
onClose={() => closeChatFirstSurfaceTab(tab)}
|
|
1939
|
+
renderEmbed={({ url, title, key }: ChatFirstEmbedTarget) => (
|
|
1940
|
+
<iframe
|
|
1941
|
+
key={key}
|
|
1942
|
+
src={url}
|
|
1943
|
+
title={title ?? chatFirstCopy("browserPage")}
|
|
1944
|
+
referrerPolicy="no-referrer"
|
|
1945
|
+
allow="clipboard-read; clipboard-write"
|
|
1946
|
+
className="h-full w-full border-0 bg-background"
|
|
1947
|
+
/>
|
|
1948
|
+
)}
|
|
1949
|
+
copy={chatFirstCopy}
|
|
1950
|
+
/>
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
if (tab.kind === "side-chat") {
|
|
1954
|
+
const target =
|
|
1955
|
+
tab.session ??
|
|
1956
|
+
(tab.id === chatFirstSurfaceTabs.activeTabId
|
|
1957
|
+
? chatFirstSessionWatch.target
|
|
1958
|
+
: null);
|
|
1959
|
+
return target ? (
|
|
1960
|
+
<ChatFirstSessionWatchPane
|
|
1961
|
+
target={target}
|
|
1962
|
+
onClose={() => closeChatFirstSurfaceTab(tab)}
|
|
1963
|
+
renderChat={renderChatFirstWatchChat}
|
|
1964
|
+
copy={chatFirstCopy}
|
|
1965
|
+
/>
|
|
1966
|
+
) : null;
|
|
1967
|
+
}
|
|
1968
|
+
if (tab.kind === "agents") {
|
|
1969
|
+
return (
|
|
1970
|
+
<ChatFirstAgentsPane
|
|
1971
|
+
activities={chatFirstAgentActivities}
|
|
1972
|
+
loading={chatFirstAgentsQuery.isLoading}
|
|
1973
|
+
error={
|
|
1974
|
+
chatFirstAgentsQuery.isError
|
|
1975
|
+
? chatFirstAgentsQuery.error.message
|
|
1976
|
+
: null
|
|
1977
|
+
}
|
|
1978
|
+
onRefresh={() => void chatFirstAgentsQuery.refetch()}
|
|
1979
|
+
onWatch={(activity) =>
|
|
1980
|
+
emitChatFirstSessionWatch({
|
|
1981
|
+
sessionId: activity.sessionId,
|
|
1982
|
+
title: activity.title,
|
|
1983
|
+
kind: "agent-chat",
|
|
1984
|
+
...(activity.goalId ? { goalId: activity.goalId } : {}),
|
|
1985
|
+
})
|
|
1986
|
+
}
|
|
1987
|
+
copy={chatFirstCopy}
|
|
1988
|
+
/>
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
return null;
|
|
1992
|
+
},
|
|
1993
|
+
[
|
|
1994
|
+
chatFirstAgentActivities,
|
|
1995
|
+
chatFirstAgentsQuery.error,
|
|
1996
|
+
chatFirstAgentsQuery.isError,
|
|
1997
|
+
chatFirstAgentsQuery.isLoading,
|
|
1998
|
+
chatFirstAppsQuery.isLoading,
|
|
1999
|
+
chatFirstCopy,
|
|
2000
|
+
chatFirstEmbedError,
|
|
2001
|
+
chatFirstEmbedUrl,
|
|
2002
|
+
chatFirstSessionWatch.target,
|
|
2003
|
+
chatFirstSurfaceTabs.activeTabId,
|
|
2004
|
+
chatFirstWorkspaceApps,
|
|
2005
|
+
closeChatFirstSurfaceTab,
|
|
2006
|
+
renderChatFirstWatchChat,
|
|
2007
|
+
],
|
|
2008
|
+
);
|
|
2009
|
+
|
|
808
2010
|
if (CHROMELESS_PATHS.some((path) => localPathname === path)) {
|
|
809
2011
|
return <>{children}</>;
|
|
810
2012
|
}
|
|
811
2013
|
|
|
2014
|
+
if (isWorkspaceAppHostRoute) {
|
|
2015
|
+
return (
|
|
2016
|
+
<DispatchExtensionsContext.Provider value={extensions}>
|
|
2017
|
+
<HeaderActionsProvider>
|
|
2018
|
+
<div
|
|
2019
|
+
data-dispatch-workspace-app-chrome-less
|
|
2020
|
+
className="h-screen w-full overflow-hidden bg-background"
|
|
2021
|
+
>
|
|
2022
|
+
{children}
|
|
2023
|
+
</div>
|
|
2024
|
+
</HeaderActionsProvider>
|
|
2025
|
+
</DispatchExtensionsContext.Provider>
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
|
|
812
2029
|
const showHeader = !isChatRoute && !pageOwnsToolbar(localPathname);
|
|
813
2030
|
function openAskAgentFullscreen() {
|
|
814
2031
|
focusAgentChat();
|
|
@@ -823,9 +2040,32 @@ export function Layout({
|
|
|
823
2040
|
t("dispatch.sidebar.suggestionGrantKey"),
|
|
824
2041
|
];
|
|
825
2042
|
const appContent = (
|
|
826
|
-
<div className="flex h-full min-w-0 flex-1 flex-col overflow-hidden">
|
|
2043
|
+
<div className="relative flex h-full min-w-0 flex-1 flex-col overflow-hidden">
|
|
827
2044
|
{showHeader ? <Header onOpenMobile={() => setMobileOpen(true)} /> : null}
|
|
828
2045
|
<InvitationBanner />
|
|
2046
|
+
{isChatRoute && chatFirstMode ? (
|
|
2047
|
+
<ChatFirstSurfacePanelToggle
|
|
2048
|
+
open={chatFirstSurfacePanel.open}
|
|
2049
|
+
onToggle={chatFirstSurfacePanel.toggle}
|
|
2050
|
+
className="absolute right-3 top-2 z-[4]"
|
|
2051
|
+
/>
|
|
2052
|
+
) : null}
|
|
2053
|
+
{isChatRoute && chatFirstMode && chatFirstNotice ? (
|
|
2054
|
+
<div
|
|
2055
|
+
className="dispatch-chat-first-notice"
|
|
2056
|
+
role="status"
|
|
2057
|
+
aria-live="polite"
|
|
2058
|
+
>
|
|
2059
|
+
<span>{chatFirstNotice}</span>
|
|
2060
|
+
<button
|
|
2061
|
+
type="button"
|
|
2062
|
+
onClick={() => setChatFirstNotice(null)}
|
|
2063
|
+
aria-label="Dismiss app notice"
|
|
2064
|
+
>
|
|
2065
|
+
Dismiss
|
|
2066
|
+
</button>
|
|
2067
|
+
</div>
|
|
2068
|
+
) : null}
|
|
829
2069
|
<main
|
|
830
2070
|
className={cn(
|
|
831
2071
|
"flex-1",
|
|
@@ -845,8 +2085,52 @@ export function Layout({
|
|
|
845
2085
|
</div>
|
|
846
2086
|
);
|
|
847
2087
|
const content = isChatRoute ? (
|
|
848
|
-
<div
|
|
2088
|
+
<div
|
|
2089
|
+
className={cn(
|
|
2090
|
+
"agent-layout-main-surface flex min-w-0 flex-1 overflow-hidden",
|
|
2091
|
+
chatFirstMode && "dispatch-chat-first-surface",
|
|
2092
|
+
)}
|
|
2093
|
+
>
|
|
849
2094
|
{appContent}
|
|
2095
|
+
{chatFirstMode && chatFirstSurfacePanel.open ? (
|
|
2096
|
+
<ChatFirstSurfacePanel
|
|
2097
|
+
width={chatFirstSurfaceResize.width}
|
|
2098
|
+
onResizePointerDown={chatFirstSurfaceResize.onPointerDown}
|
|
2099
|
+
copy={chatFirstCopy}
|
|
2100
|
+
>
|
|
2101
|
+
<ChatFirstSurfaceTabs
|
|
2102
|
+
tabs={chatFirstSurfaceTabs.tabs}
|
|
2103
|
+
activeTabId={chatFirstSurfaceTabs.activeTabId}
|
|
2104
|
+
onActivate={activateChatFirstSurfaceTab}
|
|
2105
|
+
onClose={closeChatFirstSurfaceTab}
|
|
2106
|
+
onCloseOthers={(tab) => {
|
|
2107
|
+
activateChatFirstSurfaceTab(tab);
|
|
2108
|
+
chatFirstSurfaceTabsStore.closeOthers(tab.id);
|
|
2109
|
+
}}
|
|
2110
|
+
onCloseToRight={(tab) => {
|
|
2111
|
+
const targetIndex = chatFirstSurfaceTabs.tabs.findIndex(
|
|
2112
|
+
(candidate) => candidate.id === tab.id,
|
|
2113
|
+
);
|
|
2114
|
+
const activeIndex = chatFirstSurfaceTabs.tabs.findIndex(
|
|
2115
|
+
(candidate) =>
|
|
2116
|
+
candidate.id === chatFirstSurfaceTabs.activeTabId,
|
|
2117
|
+
);
|
|
2118
|
+
if (activeIndex > targetIndex) activateChatFirstSurfaceTab(tab);
|
|
2119
|
+
chatFirstSurfaceTabsStore.closeToRight(tab.id);
|
|
2120
|
+
}}
|
|
2121
|
+
onCloseAll={closeAllChatFirstSurfaceTabs}
|
|
2122
|
+
onOpenSurface={openChatFirstSurface}
|
|
2123
|
+
copy={chatFirstCopy}
|
|
2124
|
+
/>
|
|
2125
|
+
{chatFirstSurfaceTabs.tabs.length > 0 ? (
|
|
2126
|
+
<ChatFirstSurfaceContent
|
|
2127
|
+
tabs={chatFirstSurfaceTabs.tabs}
|
|
2128
|
+
activeTabId={chatFirstSurfaceTabs.activeTabId}
|
|
2129
|
+
renderTab={renderChatFirstSurfaceTab}
|
|
2130
|
+
/>
|
|
2131
|
+
) : null}
|
|
2132
|
+
</ChatFirstSurfacePanel>
|
|
2133
|
+
) : null}
|
|
850
2134
|
</div>
|
|
851
2135
|
) : (
|
|
852
2136
|
<AgentSidebar
|
|
@@ -877,7 +2161,25 @@ export function Layout({
|
|
|
877
2161
|
>
|
|
878
2162
|
<NavContent
|
|
879
2163
|
extensions={extensions}
|
|
2164
|
+
chatFirstMode={chatFirstMode}
|
|
2165
|
+
chatFirstEmbedded={chatFirstEmbedded}
|
|
880
2166
|
collapsed={sidebarCollapsed}
|
|
2167
|
+
chatFirstAppLayout={chatFirstAppLayout}
|
|
2168
|
+
onChatFirstAppLayoutChange={persistChatFirstAppLayout}
|
|
2169
|
+
chatFirstApps={chatFirstAppItems}
|
|
2170
|
+
chatFirstAppsLoading={chatFirstAppsQuery.isLoading}
|
|
2171
|
+
chatFirstAppsError={
|
|
2172
|
+
chatFirstAppsQuery.isError
|
|
2173
|
+
? chatFirstCopy("appsLoadError")
|
|
2174
|
+
: null
|
|
2175
|
+
}
|
|
2176
|
+
chatFirstActiveAppId={
|
|
2177
|
+
activeChatFirstSurfaceTab?.kind === "app"
|
|
2178
|
+
? activeChatFirstSurfaceTab.appId
|
|
2179
|
+
: undefined
|
|
2180
|
+
}
|
|
2181
|
+
onChatFirstAppOpen={(app) => openChatFirstPane({ appId: app.id })}
|
|
2182
|
+
onChatFirstAppsRetry={() => void chatFirstAppsQuery.refetch()}
|
|
881
2183
|
collapsible
|
|
882
2184
|
onCollapsedChange={setSidebarCollapsed}
|
|
883
2185
|
/>
|
|
@@ -897,7 +2199,27 @@ export function Layout({
|
|
|
897
2199
|
<div className="flex h-full w-full flex-col">
|
|
898
2200
|
<NavContent
|
|
899
2201
|
extensions={extensions}
|
|
2202
|
+
chatFirstMode={chatFirstMode}
|
|
2203
|
+
chatFirstEmbedded={chatFirstEmbedded}
|
|
900
2204
|
collapsed={false}
|
|
2205
|
+
chatFirstAppLayout={chatFirstAppLayout}
|
|
2206
|
+
onChatFirstAppLayoutChange={persistChatFirstAppLayout}
|
|
2207
|
+
chatFirstApps={chatFirstAppItems}
|
|
2208
|
+
chatFirstAppsLoading={chatFirstAppsQuery.isLoading}
|
|
2209
|
+
chatFirstAppsError={
|
|
2210
|
+
chatFirstAppsQuery.isError
|
|
2211
|
+
? chatFirstCopy("appsLoadError")
|
|
2212
|
+
: null
|
|
2213
|
+
}
|
|
2214
|
+
chatFirstActiveAppId={
|
|
2215
|
+
activeChatFirstSurfaceTab?.kind === "app"
|
|
2216
|
+
? activeChatFirstSurfaceTab.appId
|
|
2217
|
+
: undefined
|
|
2218
|
+
}
|
|
2219
|
+
onChatFirstAppOpen={(app) =>
|
|
2220
|
+
openChatFirstPane({ appId: app.id })
|
|
2221
|
+
}
|
|
2222
|
+
onChatFirstAppsRetry={() => void chatFirstAppsQuery.refetch()}
|
|
901
2223
|
onNavigate={() => setMobileOpen(false)}
|
|
902
2224
|
/>
|
|
903
2225
|
</div>
|