@agent-native/dispatch 0.24.0 → 0.24.3
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 +12 -12
- package/dist/components/admin-navigation.d.ts.map +1 -1
- package/dist/components/admin-navigation.js +21 -5
- package/dist/components/admin-navigation.js.map +1 -1
- package/dist/components/agents-panel.d.ts.map +1 -1
- package/dist/components/agents-panel.js +7 -2
- package/dist/components/agents-panel.js.map +1 -1
- package/dist/components/app-list-row.d.ts +1 -1
- package/dist/components/app-list-row.d.ts.map +1 -1
- package/dist/components/app-list-row.js +1 -1
- package/dist/components/app-list-row.js.map +1 -1
- package/dist/components/app-open-actions.d.ts +1 -17
- package/dist/components/app-open-actions.d.ts.map +1 -1
- package/dist/components/app-open-actions.js +1 -20
- package/dist/components/app-open-actions.js.map +1 -1
- package/dist/components/app-row-settings.d.ts +6 -0
- package/dist/components/app-row-settings.d.ts.map +1 -0
- package/dist/components/app-row-settings.js +11 -0
- package/dist/components/app-row-settings.js.map +1 -0
- package/dist/components/automation-run-history-dialog.d.ts.map +1 -1
- package/dist/components/automation-run-history-dialog.js +3 -2
- package/dist/components/automation-run-history-dialog.js.map +1 -1
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +1 -1
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/create-app-popover.js +2 -2
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +23 -7
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/dispatch-shell.d.ts.map +1 -1
- package/dist/components/dispatch-shell.js +5 -1
- package/dist/components/dispatch-shell.js.map +1 -1
- package/dist/components/docs-link.d.ts +16 -0
- package/dist/components/docs-link.d.ts.map +1 -0
- package/dist/components/docs-link.js +34 -0
- package/dist/components/docs-link.js.map +1 -0
- package/dist/components/layout/Layout.d.ts +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +37 -28
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/messaging-setup-panel.d.ts.map +1 -1
- package/dist/components/messaging-setup-panel.js +3 -2
- package/dist/components/messaging-setup-panel.js.map +1 -1
- package/dist/components/usage-alerts-panel.d.ts +11 -0
- package/dist/components/usage-alerts-panel.d.ts.map +1 -0
- package/dist/components/usage-alerts-panel.js +125 -0
- package/dist/components/usage-alerts-panel.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts +3 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +70 -29
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-app-search.d.ts +10 -0
- package/dist/components/workspace-app-search.d.ts.map +1 -0
- package/dist/components/workspace-app-search.js +27 -0
- package/dist/components/workspace-app-search.js.map +1 -0
- package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
- package/dist/components/workspace-resource-effective-stack.js +3 -2
- package/dist/components/workspace-resource-effective-stack.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +2 -1
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/lib/workspace-app-layout.d.ts +19 -0
- package/dist/lib/workspace-app-layout.d.ts.map +1 -0
- package/dist/lib/workspace-app-layout.js +75 -0
- package/dist/lib/workspace-app-layout.js.map +1 -0
- package/dist/lib/workspace-apps.d.ts +10 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +59 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/lib/workspace-apps.test.js +18 -1
- package/dist/lib/workspace-apps.test.js.map +1 -1
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +4 -2
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/agents.d.ts.map +1 -1
- package/dist/routes/pages/agents.js +2 -3
- package/dist/routes/pages/agents.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +8 -3
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +31 -16
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/audit.d.ts.map +1 -1
- package/dist/routes/pages/audit.js +2 -1
- package/dist/routes/pages/audit.js.map +1 -1
- package/dist/routes/pages/dreams.d.ts.map +1 -1
- package/dist/routes/pages/dreams.js +14 -18
- package/dist/routes/pages/dreams.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +51 -14
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +4 -1
- package/dist/routes/pages/metrics.js.map +1 -1
- package/dist/routes/pages/operations.js +1 -1
- package/dist/routes/pages/operations.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +7 -2
- 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 +5 -8
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +1 -1
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/routes/pages/workspace.d.ts.map +1 -1
- package/dist/routes/pages/workspace.js +13 -10
- package/dist/routes/pages/workspace.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- 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 +12 -52
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/shared/app-roles.d.ts +9 -0
- package/dist/shared/app-roles.d.ts.map +1 -0
- package/dist/shared/app-roles.js +7 -0
- package/dist/shared/app-roles.js.map +1 -0
- package/package.json +4 -3
- package/src/components/admin-navigation.tsx +57 -11
- package/src/components/agents-panel.tsx +54 -29
- package/src/components/app-list-row.tsx +1 -2
- package/src/components/app-open-actions.tsx +5 -114
- package/src/components/app-row-settings.tsx +54 -0
- package/src/components/automation-run-history-dialog.tsx +8 -4
- package/src/components/connected-app-card.tsx +0 -1
- package/src/components/create-app-popover.spec.tsx +1 -1
- package/src/components/create-app-popover.tsx +2 -2
- package/src/components/dispatch-control-plane.spec.tsx +36 -2
- package/src/components/dispatch-control-plane.tsx +83 -27
- package/src/components/dispatch-shell.tsx +7 -1
- package/src/components/docs-link.tsx +52 -0
- package/src/components/layout/Layout.spec.tsx +10 -0
- package/src/components/layout/Layout.tsx +50 -23
- package/src/components/messaging-setup-panel.tsx +10 -3
- package/src/components/usage-alerts-panel.tsx +445 -0
- package/src/components/workspace-app-card.spec.tsx +130 -9
- package/src/components/workspace-app-card.tsx +128 -71
- package/src/components/workspace-app-search.tsx +96 -0
- package/src/components/workspace-resource-effective-stack.tsx +10 -5
- package/src/components/workspace-template-card.spec.tsx +52 -23
- package/src/components/workspace-template-card.tsx +7 -10
- package/src/lib/workspace-app-layout.spec.ts +47 -0
- package/src/lib/workspace-app-layout.ts +121 -0
- package/src/lib/workspace-apps.test.ts +45 -1
- package/src/lib/workspace-apps.ts +72 -0
- package/src/routes/pages/$appId.tsx +3 -6
- package/src/routes/pages/agents.tsx +6 -3
- package/src/routes/pages/apps.$appId.tsx +6 -6
- package/src/routes/pages/apps.tsx +116 -62
- package/src/routes/pages/audit.tsx +28 -8
- package/src/routes/pages/dreams.tsx +92 -142
- package/src/routes/pages/integrations.tsx +147 -68
- package/src/routes/pages/metrics.tsx +8 -0
- package/src/routes/pages/operations.tsx +2 -2
- package/src/routes/pages/settings.tsx +14 -9
- package/src/routes/pages/thread-debug.tsx +18 -36
- package/src/routes/pages/vault.tsx +7 -2
- package/src/routes/pages/workspace.tsx +92 -56
- package/src/server/index.ts +4 -0
- package/src/server/lib/app-creation-store.spec.ts +90 -43
- package/src/server/lib/app-creation-store.ts +15 -61
- package/src/shared/app-roles.ts +8 -0
|
@@ -12,6 +12,11 @@ import { Link, useNavigate } from "react-router";
|
|
|
12
12
|
|
|
13
13
|
import type { ConnectedAppSummary } from "../lib/other-apps";
|
|
14
14
|
import { cn } from "../lib/utils";
|
|
15
|
+
import {
|
|
16
|
+
orderWorkspaceApps,
|
|
17
|
+
useWorkspaceAppLayout,
|
|
18
|
+
workspaceAppMatchesQuery,
|
|
19
|
+
} from "../lib/workspace-app-layout";
|
|
15
20
|
import {
|
|
16
21
|
isWorkspaceAppVisibleInDefaultLaunchers,
|
|
17
22
|
type WorkspaceAppSummary,
|
|
@@ -33,6 +38,10 @@ import {
|
|
|
33
38
|
} from "./ui/collapsible";
|
|
34
39
|
import { Skeleton } from "./ui/skeleton";
|
|
35
40
|
import { WorkspaceAppCard } from "./workspace-app-card";
|
|
41
|
+
import {
|
|
42
|
+
WorkspaceAppSearch,
|
|
43
|
+
WorkspaceAppSearchEmpty,
|
|
44
|
+
} from "./workspace-app-search";
|
|
36
45
|
import type { CuratedWorkspaceTemplatesResult } from "./workspace-template-card";
|
|
37
46
|
|
|
38
47
|
function SectionHeader({
|
|
@@ -65,9 +74,6 @@ function CommandPanel() {
|
|
|
65
74
|
} = useChatModels({ storageKey: "dispatch" });
|
|
66
75
|
const navigate = useNavigate();
|
|
67
76
|
const promptSuggestions = [
|
|
68
|
-
t("dispatch.pages.suggestionWorkspaceHealth", {
|
|
69
|
-
defaultValue: "Summarize the current workspace health",
|
|
70
|
-
}),
|
|
71
77
|
t("dispatch.pages.suggestionOnboardingApp", {
|
|
72
78
|
defaultValue: "Create an app for onboarding requests",
|
|
73
79
|
}),
|
|
@@ -165,11 +171,23 @@ function AppsPanel({
|
|
|
165
171
|
}) {
|
|
166
172
|
const t = useT();
|
|
167
173
|
const [showPending, setShowPending] = useState(false);
|
|
174
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
175
|
+
const { layout, persistenceError, togglePinned } = useWorkspaceAppLayout();
|
|
168
176
|
const visibleApps = apps.filter(
|
|
169
177
|
(app) => isWorkspaceAppVisibleInDefaultLaunchers(app) && !app.archived,
|
|
170
178
|
);
|
|
171
179
|
const activeApps = visibleApps.filter((app) => app.status !== "pending");
|
|
172
180
|
const pendingApps = visibleApps.filter((app) => app.status === "pending");
|
|
181
|
+
const orderedActiveApps = orderWorkspaceApps(activeApps, layout);
|
|
182
|
+
const orderedPendingApps = orderWorkspaceApps(pendingApps, layout);
|
|
183
|
+
const filteredActiveApps = orderedActiveApps.filter((app) =>
|
|
184
|
+
workspaceAppMatchesQuery(app, searchQuery),
|
|
185
|
+
);
|
|
186
|
+
const filteredPendingApps = orderedPendingApps.filter((app) =>
|
|
187
|
+
workspaceAppMatchesQuery(app, searchQuery),
|
|
188
|
+
);
|
|
189
|
+
const hasSearchResults =
|
|
190
|
+
filteredActiveApps.length > 0 || filteredPendingApps.length > 0;
|
|
173
191
|
const otherAppEntries = mergeOtherAppEntries({
|
|
174
192
|
templates: curatedTemplates,
|
|
175
193
|
connectedApps,
|
|
@@ -183,14 +201,23 @@ function AppsPanel({
|
|
|
183
201
|
<SectionHeader
|
|
184
202
|
title="Apps"
|
|
185
203
|
action={
|
|
186
|
-
<div className="flex items-center gap-1.5">
|
|
187
|
-
<Button variant="
|
|
188
|
-
<Link to="/apps"
|
|
204
|
+
<div className="flex min-w-0 flex-wrap items-center justify-end gap-1.5">
|
|
205
|
+
<Button variant="ghost" size="sm" asChild>
|
|
206
|
+
<Link to="/apps" className="text-muted-foreground">
|
|
207
|
+
View all
|
|
208
|
+
</Link>
|
|
189
209
|
</Button>
|
|
210
|
+
{!showSkeletons && visibleApps.length > 0 ? (
|
|
211
|
+
<WorkspaceAppSearch
|
|
212
|
+
className="w-[220px]"
|
|
213
|
+
query={searchQuery}
|
|
214
|
+
onQueryChange={setSearchQuery}
|
|
215
|
+
/>
|
|
216
|
+
) : null}
|
|
190
217
|
<CreateAppPopover
|
|
191
218
|
align="end"
|
|
192
219
|
trigger={
|
|
193
|
-
<Button variant="
|
|
220
|
+
<Button variant="default" size="sm" className="gap-1.5">
|
|
194
221
|
<IconPlus size={14} />
|
|
195
222
|
{t("dispatch.pages.newApp", { defaultValue: "New" })}
|
|
196
223
|
</Button>
|
|
@@ -199,19 +226,38 @@ function AppsPanel({
|
|
|
199
226
|
</div>
|
|
200
227
|
}
|
|
201
228
|
/>
|
|
229
|
+
{!showSkeletons && visibleApps.length > 0 && persistenceError ? (
|
|
230
|
+
<p className="text-xs text-muted-foreground" role="status">
|
|
231
|
+
{persistenceError === "both"
|
|
232
|
+
? t("dispatch.pages.appPinSaveFailed", {
|
|
233
|
+
defaultValue: "App pins could not be saved.",
|
|
234
|
+
})
|
|
235
|
+
: t("dispatch.pages.appPinSavedLocally", {
|
|
236
|
+
defaultValue: "App pins are saved on this device only.",
|
|
237
|
+
})}
|
|
238
|
+
</p>
|
|
239
|
+
) : null}
|
|
202
240
|
{showSkeletons ? (
|
|
203
241
|
<OverviewAppsSkeleton />
|
|
242
|
+
) : searchQuery.trim() && !hasSearchResults ? (
|
|
243
|
+
<WorkspaceAppSearchEmpty
|
|
244
|
+
query={searchQuery}
|
|
245
|
+
onClear={() => setSearchQuery("")}
|
|
246
|
+
/>
|
|
204
247
|
) : (
|
|
205
248
|
<>
|
|
206
249
|
<AppList className={APP_LIST_GRID_CLASS}>
|
|
207
|
-
{
|
|
250
|
+
{filteredActiveApps.map((app) => (
|
|
208
251
|
<WorkspaceAppCard
|
|
209
252
|
key={app.id}
|
|
210
253
|
app={app}
|
|
211
254
|
className={APP_LIST_GRID_ROW_CLASS}
|
|
255
|
+
isPinned={layout.pinnedIds.includes(app.id)}
|
|
256
|
+
onTogglePinned={() => togglePinned(app.id)}
|
|
212
257
|
/>
|
|
213
258
|
))}
|
|
214
|
-
{
|
|
259
|
+
{filteredActiveApps.length === 0 &&
|
|
260
|
+
!searchQuery.trim() &&
|
|
215
261
|
otherAppEntries.length === 0 &&
|
|
216
262
|
!curatedTemplatesLoading &&
|
|
217
263
|
!connectedAppsLoading &&
|
|
@@ -223,24 +269,30 @@ function AppsPanel({
|
|
|
223
269
|
})}
|
|
224
270
|
</p>
|
|
225
271
|
) : null}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
272
|
+
{!searchQuery.trim() ? (
|
|
273
|
+
<OtherAppsSection
|
|
274
|
+
templates={curatedTemplates}
|
|
275
|
+
connectedApps={connectedApps}
|
|
276
|
+
workspaceApps={apps}
|
|
277
|
+
templatesLoading={curatedTemplatesLoading}
|
|
278
|
+
connectedAppsLoading={connectedAppsLoading}
|
|
279
|
+
templatesError={curatedTemplatesError}
|
|
280
|
+
connectedAppsError={connectedAppsError}
|
|
281
|
+
onRetryTemplates={onRetryCuratedTemplates}
|
|
282
|
+
onRetryConnectedApps={onRetryConnectedApps}
|
|
283
|
+
heading={null}
|
|
284
|
+
embeddedInList
|
|
285
|
+
/>
|
|
286
|
+
) : null}
|
|
239
287
|
</AppList>
|
|
240
288
|
</>
|
|
241
289
|
)}
|
|
242
|
-
{pendingApps.length > 0
|
|
243
|
-
|
|
290
|
+
{pendingApps.length > 0 &&
|
|
291
|
+
(!searchQuery.trim() || filteredPendingApps.length > 0) ? (
|
|
292
|
+
<Collapsible
|
|
293
|
+
open={showPending || Boolean(searchQuery.trim())}
|
|
294
|
+
onOpenChange={setShowPending}
|
|
295
|
+
>
|
|
244
296
|
<div className="space-y-3 border-t pt-3">
|
|
245
297
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
246
298
|
<div className="flex min-w-0 items-center gap-2">
|
|
@@ -266,19 +318,23 @@ function AppsPanel({
|
|
|
266
318
|
size="sm"
|
|
267
319
|
className="h-7 gap-1.5 px-2 text-xs"
|
|
268
320
|
>
|
|
269
|
-
{showPending
|
|
321
|
+
{showPending || Boolean(searchQuery.trim())
|
|
270
322
|
? t("dispatch.pages.hidePendingApps")
|
|
271
323
|
: t("dispatch.pages.showPendingApps")}
|
|
272
324
|
<IconChevronDown
|
|
273
325
|
size={13}
|
|
274
|
-
className={
|
|
326
|
+
className={
|
|
327
|
+
showPending || searchQuery.trim()
|
|
328
|
+
? "rotate-180"
|
|
329
|
+
: undefined
|
|
330
|
+
}
|
|
275
331
|
/>
|
|
276
332
|
</Button>
|
|
277
333
|
</CollapsibleTrigger>
|
|
278
334
|
</div>
|
|
279
335
|
<CollapsibleContent>
|
|
280
336
|
<AppList className={APP_LIST_GRID_CLASS}>
|
|
281
|
-
{
|
|
337
|
+
{filteredPendingApps.map((app) => (
|
|
282
338
|
<WorkspaceAppCard
|
|
283
339
|
key={app.id}
|
|
284
340
|
app={app}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
2
|
import { IconInfoCircle } from "@tabler/icons-react";
|
|
3
3
|
import { type ReactNode } from "react";
|
|
4
|
+
import { useLocation } from "react-router";
|
|
4
5
|
|
|
6
|
+
import { dispatchDocsHrefForPath, DocsLink } from "./docs-link";
|
|
5
7
|
import { useSetPageTitle } from "./layout/HeaderActions";
|
|
6
8
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
|
7
9
|
|
|
@@ -21,12 +23,16 @@ export function DispatchShell({
|
|
|
21
23
|
children: ReactNode;
|
|
22
24
|
}) {
|
|
23
25
|
const t = useT();
|
|
26
|
+
const location = useLocation();
|
|
27
|
+
const docsHref = dispatchDocsHrefForPath(location.pathname);
|
|
24
28
|
useSetPageTitle(
|
|
25
29
|
<div className="flex items-center gap-2 min-w-0">
|
|
26
30
|
<h1 className="text-lg font-semibold tracking-tight truncate text-foreground">
|
|
27
31
|
{title}
|
|
28
32
|
</h1>
|
|
29
|
-
{
|
|
33
|
+
{docsHref ? (
|
|
34
|
+
<DocsLink href={docsHref} label={`Open ${title} documentation`} />
|
|
35
|
+
) : description ? (
|
|
30
36
|
<Popover>
|
|
31
37
|
<PopoverTrigger asChild>
|
|
32
38
|
<button
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { IconHelpCircle } from "@tabler/icons-react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
export const DISPATCH_DOCS = {
|
|
6
|
+
dispatch: "https://agent-native.com/docs/dispatch#what-it-does",
|
|
7
|
+
workspace: "https://agent-native.com/docs/agent-resources#global-resources",
|
|
8
|
+
integrations: "https://agent-native.com/docs/dispatch#secret-vault",
|
|
9
|
+
audit: "https://agent-native.com/docs/audit-log",
|
|
10
|
+
threadDebug: "https://agent-native.com/docs/dispatch#reliability",
|
|
11
|
+
dreams: "https://agent-native.com/docs/dispatch#dreams",
|
|
12
|
+
settings: "https://agent-native.com/docs/agent-surfaces",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export function dispatchDocsHrefForPath(pathname: string): string | undefined {
|
|
16
|
+
if (pathname === "/admin/workspace") return DISPATCH_DOCS.workspace;
|
|
17
|
+
if (pathname === "/admin/integrations") return DISPATCH_DOCS.integrations;
|
|
18
|
+
if (pathname === "/admin/audit") return DISPATCH_DOCS.audit;
|
|
19
|
+
if (pathname === "/admin/thread-debug") return DISPATCH_DOCS.threadDebug;
|
|
20
|
+
if (pathname === "/admin/dreams") return DISPATCH_DOCS.dreams;
|
|
21
|
+
if (pathname.startsWith("/admin/")) return DISPATCH_DOCS.dispatch;
|
|
22
|
+
if (pathname === "/settings" || pathname.startsWith("/settings/")) {
|
|
23
|
+
return DISPATCH_DOCS.settings;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function DocsLink({
|
|
29
|
+
href,
|
|
30
|
+
label,
|
|
31
|
+
className,
|
|
32
|
+
}: {
|
|
33
|
+
href: string;
|
|
34
|
+
label: string;
|
|
35
|
+
className?: string;
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<a
|
|
39
|
+
href={href}
|
|
40
|
+
target="_blank"
|
|
41
|
+
rel="noopener noreferrer"
|
|
42
|
+
aria-label={label}
|
|
43
|
+
title={label}
|
|
44
|
+
className={cn(
|
|
45
|
+
"inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent/60 hover:text-foreground",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<IconHelpCircle className="size-3" />
|
|
50
|
+
</a>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
formatThreadAge,
|
|
12
12
|
isElectronEmbeddedSearch,
|
|
13
13
|
NavContent,
|
|
14
|
+
shouldAutoCollapseDispatchSidebar,
|
|
14
15
|
} from "./Layout";
|
|
15
16
|
|
|
16
17
|
const clientState = vi.hoisted(() => ({
|
|
@@ -128,6 +129,15 @@ describe("Electron control-plane mode", () => {
|
|
|
128
129
|
});
|
|
129
130
|
});
|
|
130
131
|
|
|
132
|
+
describe("Dispatch workspace app sidebar", () => {
|
|
133
|
+
it("auto-collapses for app host routes but not the app catalog", () => {
|
|
134
|
+
expect(shouldAutoCollapseDispatchSidebar("/apps/mail")).toBe(true);
|
|
135
|
+
expect(shouldAutoCollapseDispatchSidebar("/apps/mail/settings")).toBe(true);
|
|
136
|
+
expect(shouldAutoCollapseDispatchSidebar("/apps")).toBe(false);
|
|
137
|
+
expect(shouldAutoCollapseDispatchSidebar("/chat")).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
131
141
|
describe("Dispatch NavContent", () => {
|
|
132
142
|
let container: HTMLDivElement;
|
|
133
143
|
let root: Root;
|
|
@@ -108,6 +108,7 @@ import {
|
|
|
108
108
|
isDispatchWorkspaceAppId,
|
|
109
109
|
isWorkspaceAppVisibleInDefaultLaunchers,
|
|
110
110
|
mergeChatFirstWorkspaceApps,
|
|
111
|
+
workspaceAppDirectHref,
|
|
111
112
|
workspaceAppIdFromRoute,
|
|
112
113
|
workspaceAppRoute,
|
|
113
114
|
type WorkspaceAppSummary,
|
|
@@ -306,6 +307,10 @@ export function isElectronEmbeddedSearch(search: string): boolean {
|
|
|
306
307
|
return new URLSearchParams(search).get("electron") === "1";
|
|
307
308
|
}
|
|
308
309
|
|
|
310
|
+
export function shouldAutoCollapseDispatchSidebar(pathname: string): boolean {
|
|
311
|
+
return localDispatchPath(pathname).startsWith("/apps/");
|
|
312
|
+
}
|
|
313
|
+
|
|
309
314
|
function chatFirstPrimaryTabForPath(
|
|
310
315
|
pathname: string,
|
|
311
316
|
): ChatFirstPrimaryTab | undefined {
|
|
@@ -534,16 +539,6 @@ function threadTitle(thread: ChatThreadSummary, fallback: string) {
|
|
|
534
539
|
return thread.title || thread.preview || fallback;
|
|
535
540
|
}
|
|
536
541
|
|
|
537
|
-
function readChatHistoryIncludesExternal(): boolean {
|
|
538
|
-
if (typeof window === "undefined") return false;
|
|
539
|
-
try {
|
|
540
|
-
return localStorage.getItem(CHAT_HISTORY_SOURCE_KEY) === "all";
|
|
541
|
-
} catch {
|
|
542
|
-
// coercion-ok: localStorage is optional browser persistence.
|
|
543
|
-
return false;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
542
|
function threadSourceIcon(platform: string | undefined): ReactNode {
|
|
548
543
|
const normalized = platform?.trim().toLowerCase();
|
|
549
544
|
if (!normalized) return null;
|
|
@@ -556,6 +551,16 @@ function threadSourceIcon(platform: string | undefined): ReactNode {
|
|
|
556
551
|
return <IconWorld size={13} aria-hidden="true" />;
|
|
557
552
|
}
|
|
558
553
|
|
|
554
|
+
function readChatHistoryIncludesExternal(): boolean {
|
|
555
|
+
if (typeof window === "undefined") return false;
|
|
556
|
+
try {
|
|
557
|
+
return localStorage.getItem(CHAT_HISTORY_SOURCE_KEY) === "all";
|
|
558
|
+
} catch {
|
|
559
|
+
// coercion-ok: localStorage is optional browser persistence.
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
559
564
|
function threadUpdatedAt(thread: ChatThreadSummary) {
|
|
560
565
|
return Number.isFinite(thread.updatedAt)
|
|
561
566
|
? thread.updatedAt
|
|
@@ -595,6 +600,7 @@ function DispatchChatsSection({
|
|
|
595
600
|
threads,
|
|
596
601
|
activeThreadId,
|
|
597
602
|
isLoading: chatsLoading,
|
|
603
|
+
threadsLoadError,
|
|
598
604
|
createThread,
|
|
599
605
|
switchThread,
|
|
600
606
|
renameThread,
|
|
@@ -611,7 +617,7 @@ function DispatchChatsSection({
|
|
|
611
617
|
(thread) => thread.messageCount > 0 || thread.id === activeThreadId,
|
|
612
618
|
)
|
|
613
619
|
.sort((a, b) => threadUpdatedAt(b) - threadUpdatedAt(a))
|
|
614
|
-
.slice(0,
|
|
620
|
+
.slice(0, 8),
|
|
615
621
|
[activeThreadId, threads],
|
|
616
622
|
);
|
|
617
623
|
const localPathname = localDispatchPath(location.pathname);
|
|
@@ -650,6 +656,16 @@ function DispatchChatsSection({
|
|
|
650
656
|
: formatThreadAge(threadUpdatedAt(thread)),
|
|
651
657
|
};
|
|
652
658
|
});
|
|
659
|
+
const chatHistoryError = threadsLoadError ? (
|
|
660
|
+
<button
|
|
661
|
+
type="button"
|
|
662
|
+
onClick={() => void refreshThreads()}
|
|
663
|
+
className="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-xs text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
664
|
+
>
|
|
665
|
+
<span>Could not load chats</span>
|
|
666
|
+
<span className="font-medium">Retry</span>
|
|
667
|
+
</button>
|
|
668
|
+
) : undefined;
|
|
653
669
|
|
|
654
670
|
useEffect(() => {
|
|
655
671
|
setIncludeExternal(readChatHistoryIncludesExternal());
|
|
@@ -802,6 +818,7 @@ function DispatchChatsSection({
|
|
|
802
818
|
</div>
|
|
803
819
|
}
|
|
804
820
|
label={t("dispatch.sidebar.chats", { defaultValue: "Chats" })}
|
|
821
|
+
error={chatHistoryError}
|
|
805
822
|
onSelect={(threadId) => openThread(threadId)}
|
|
806
823
|
renameMaxLength={160}
|
|
807
824
|
onRename={(threadId, title) => void renameThread(threadId, title)}
|
|
@@ -870,6 +887,7 @@ function DispatchChatsSection({
|
|
|
870
887
|
showMore: t("dispatch.sidebar.chats"),
|
|
871
888
|
showLess: t("dispatch.sidebar.chats"),
|
|
872
889
|
}}
|
|
890
|
+
error={chatHistoryError}
|
|
873
891
|
renameMaxLength={160}
|
|
874
892
|
onRename={(threadId, title) => void renameThread(threadId, title)}
|
|
875
893
|
labels={{
|
|
@@ -924,7 +942,7 @@ function DispatchChatsSection({
|
|
|
924
942
|
) : null}
|
|
925
943
|
</>
|
|
926
944
|
)}
|
|
927
|
-
className=
|
|
945
|
+
className="min-w-0 px-2"
|
|
928
946
|
/>
|
|
929
947
|
)}
|
|
930
948
|
</div>
|
|
@@ -1348,7 +1366,9 @@ export function Layout({
|
|
|
1348
1366
|
const isChatRoute =
|
|
1349
1367
|
localPathname === "/chat" || localPathname.startsWith("/chat/");
|
|
1350
1368
|
const chatFirstSurfaceScope = threadIdFromPath(localPathname) ?? "new";
|
|
1351
|
-
const isWorkspaceAppRoute =
|
|
1369
|
+
const isWorkspaceAppRoute = shouldAutoCollapseDispatchSidebar(
|
|
1370
|
+
location.pathname,
|
|
1371
|
+
);
|
|
1352
1372
|
const isWorkspaceAppHostRoute =
|
|
1353
1373
|
isWorkspaceAppRoute &&
|
|
1354
1374
|
typeof window !== "undefined" &&
|
|
@@ -1373,12 +1393,6 @@ export function Layout({
|
|
|
1373
1393
|
`${url.pathname}${url.search}${url.hash}`,
|
|
1374
1394
|
);
|
|
1375
1395
|
}, [electronEmbedded, location.pathname, location.search]);
|
|
1376
|
-
useEffect(() => {
|
|
1377
|
-
if (!chatFirstMode || chatFirstEmbedded || localPathname !== "/overview") {
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
navigate(dispatchNavLinkTarget("/chat"), { replace: true });
|
|
1381
|
-
}, [chatFirstEmbedded, chatFirstMode, localPathname, navigate]);
|
|
1382
1396
|
const [chatFirstAppLayout, setChatFirstAppLayout] =
|
|
1383
1397
|
useState<ChatFirstAppLayoutPreference>(() => readChatFirstAppLayout());
|
|
1384
1398
|
const chatFirstAppLayoutHydratedRef = useRef(false);
|
|
@@ -1565,6 +1579,13 @@ export function Layout({
|
|
|
1565
1579
|
})
|
|
1566
1580
|
.catch((cause: unknown) => {
|
|
1567
1581
|
if (!cancelled) {
|
|
1582
|
+
const directAppUrl = activeChatFirstAppRegistration
|
|
1583
|
+
? workspaceAppDirectHref(
|
|
1584
|
+
activeChatFirstAppRegistration,
|
|
1585
|
+
chatFirstEmbedPath,
|
|
1586
|
+
)
|
|
1587
|
+
: null;
|
|
1588
|
+
if (directAppUrl) setChatFirstEmbedUrl(directAppUrl);
|
|
1568
1589
|
setChatFirstEmbedError(
|
|
1569
1590
|
cause instanceof Error
|
|
1570
1591
|
? cause.message
|
|
@@ -1629,6 +1650,7 @@ export function Layout({
|
|
|
1629
1650
|
placement: ChatFirstAppSurfacePlacement = "side",
|
|
1630
1651
|
) => {
|
|
1631
1652
|
if (placement === "side" && !chatFirstHasActiveChat) return;
|
|
1653
|
+
setSidebarCollapsed(true);
|
|
1632
1654
|
setChatFirstNotice(null);
|
|
1633
1655
|
closeChatFirstSessionWatch();
|
|
1634
1656
|
if (!isChatRoute) {
|
|
@@ -2027,6 +2049,11 @@ export function Layout({
|
|
|
2027
2049
|
isChatRoute,
|
|
2028
2050
|
]);
|
|
2029
2051
|
|
|
2052
|
+
useEffect(() => {
|
|
2053
|
+
if (!isWorkspaceAppRoute) return;
|
|
2054
|
+
setSidebarCollapsed(true);
|
|
2055
|
+
}, [isWorkspaceAppRoute, localPathname]);
|
|
2056
|
+
|
|
2030
2057
|
useEffect(() => {
|
|
2031
2058
|
if (typeof window === "undefined" || isWorkspaceAppHostRoute) return;
|
|
2032
2059
|
try {
|
|
@@ -2140,10 +2167,10 @@ export function Layout({
|
|
|
2140
2167
|
<ChatFirstAppPane
|
|
2141
2168
|
app={app}
|
|
2142
2169
|
status={
|
|
2143
|
-
|
|
2144
|
-
? "
|
|
2145
|
-
:
|
|
2146
|
-
? "
|
|
2170
|
+
chatFirstEmbedUrl
|
|
2171
|
+
? "ready"
|
|
2172
|
+
: chatFirstEmbedError
|
|
2173
|
+
? "error"
|
|
2147
2174
|
: chatFirstAppsQuery.isLoading
|
|
2148
2175
|
? "loading"
|
|
2149
2176
|
: app
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
CollapsibleTrigger,
|
|
58
58
|
} from "./ui/collapsible";
|
|
59
59
|
import { Input } from "./ui/input";
|
|
60
|
+
import { Skeleton } from "./ui/skeleton";
|
|
60
61
|
import { Switch } from "./ui/switch";
|
|
61
62
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
62
63
|
|
|
@@ -553,7 +554,11 @@ export function MessagingSetupPanel() {
|
|
|
553
554
|
: "Finish setup to connect"}
|
|
554
555
|
</p>
|
|
555
556
|
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
556
|
-
{
|
|
557
|
+
{envLoading ? (
|
|
558
|
+
<Skeleton className="h-3 w-32" />
|
|
559
|
+
) : (
|
|
560
|
+
credentialSummary
|
|
561
|
+
)}
|
|
557
562
|
</p>
|
|
558
563
|
</div>
|
|
559
564
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -1115,8 +1120,10 @@ export function MessagingSetupPanel() {
|
|
|
1115
1120
|
</Accordion>
|
|
1116
1121
|
|
|
1117
1122
|
{loading ? (
|
|
1118
|
-
<div className="rounded-2xl border border-dashed px-4 py-6
|
|
1119
|
-
|
|
1123
|
+
<div className="space-y-3 rounded-2xl border border-dashed px-4 py-6">
|
|
1124
|
+
<Skeleton className="h-4 w-40" />
|
|
1125
|
+
<Skeleton className="h-3 w-2/3 max-w-md" />
|
|
1126
|
+
<Skeleton className="h-9 w-full max-w-sm" />
|
|
1120
1127
|
</div>
|
|
1121
1128
|
) : null}
|
|
1122
1129
|
</div>
|