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