@agent-native/dispatch 0.19.2 → 0.20.1
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 +5 -5
- package/dist/actions/start-workspace-app-creation.js +1 -1
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/components/admin-navigation.d.ts +2 -1
- package/dist/components/admin-navigation.d.ts.map +1 -1
- package/dist/components/admin-navigation.js +10 -7
- package/dist/components/admin-navigation.js.map +1 -1
- package/dist/components/app-icon.d.ts +9 -0
- package/dist/components/app-icon.d.ts.map +1 -0
- package/dist/components/app-icon.js +85 -0
- package/dist/components/app-icon.js.map +1 -0
- package/dist/components/app-list-row.d.ts +12 -0
- package/dist/components/app-list-row.d.ts.map +1 -0
- package/dist/components/app-list-row.js +11 -0
- package/dist/components/app-list-row.js.map +1 -0
- package/dist/components/app-open-actions.d.ts +17 -0
- package/dist/components/app-open-actions.d.ts.map +1 -0
- package/dist/components/app-open-actions.js +20 -0
- package/dist/components/app-open-actions.js.map +1 -0
- package/dist/components/connected-app-card.d.ts +2 -1
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +8 -8
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +21 -14
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +40 -19
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -1
- package/dist/components/layout/workspace-apps-rail.js +10 -10
- package/dist/components/layout/workspace-apps-rail.js.map +1 -1
- package/dist/components/other-apps-section.d.ts +31 -0
- package/dist/components/other-apps-section.d.ts.map +1 -0
- package/dist/components/other-apps-section.js +59 -0
- package/dist/components/other-apps-section.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +38 -28
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts +8 -2
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +17 -13
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/lib/other-apps.d.ts.map +1 -1
- package/dist/lib/other-apps.js +5 -1
- package/dist/lib/other-apps.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +1 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +3 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +0 -2
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/admin._index.d.ts.map +1 -1
- package/dist/routes/pages/admin._index.js +32 -7
- package/dist/routes/pages/admin._index.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +62 -13
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +21 -30
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +6 -1
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/package.json +2 -2
- package/src/actions/start-workspace-app-creation.ts +1 -1
- package/src/components/admin-navigation.tsx +21 -16
- package/src/components/app-icon.tsx +149 -0
- package/src/components/app-list-row.tsx +40 -0
- package/src/components/app-open-actions.tsx +111 -0
- package/src/components/connected-app-card.tsx +30 -34
- package/src/components/dispatch-control-plane.spec.tsx +28 -1
- package/src/components/dispatch-control-plane.tsx +122 -48
- package/src/components/layout/Layout.spec.tsx +4 -4
- package/src/components/layout/Layout.tsx +86 -55
- package/src/components/layout/workspace-apps-rail.tsx +20 -21
- package/src/components/other-apps-section.tsx +195 -0
- package/src/components/workspace-app-card.spec.tsx +58 -32
- package/src/components/workspace-app-card.tsx +222 -158
- package/src/components/workspace-template-card.spec.tsx +45 -7
- package/src/components/workspace-template-card.tsx +74 -81
- package/src/lib/other-apps.spec.ts +31 -0
- package/src/lib/other-apps.ts +9 -1
- package/src/lib/workspace-apps.ts +4 -0
- package/src/routes/index.spec.ts +2 -1
- package/src/routes/index.ts +0 -2
- package/src/routes/pages/admin._index.tsx +85 -17
- package/src/routes/pages/apps.$appId.tsx +144 -57
- package/src/routes/pages/apps.tsx +76 -147
- package/src/server/plugins/agent-chat.spec.ts +5 -0
- package/src/server/plugins/agent-chat.ts +6 -1
|
@@ -1,31 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useActionMutation,
|
|
3
|
+
useActionQuery,
|
|
4
|
+
} from "@agent-native/core/client/hooks";
|
|
2
5
|
import { useT } from "@agent-native/core/client/i18n";
|
|
3
6
|
import { withBuilderUtmTrackingParams } from "@agent-native/core/shared/builder-link-tracking";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
IconClockHour4,
|
|
8
|
-
} from "@tabler/icons-react";
|
|
9
|
-
import { useEffect, useMemo } from "react";
|
|
10
|
-
import { Link, useLocation, useParams } from "react-router";
|
|
7
|
+
import { IconArrowLeft, IconClockHour4 } from "@tabler/icons-react";
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { Link, useParams } from "react-router";
|
|
11
10
|
|
|
12
11
|
import { ActionQueryError } from "../../components/action-query-error";
|
|
13
|
-
import { DispatchShell } from "../../components/dispatch-shell";
|
|
14
12
|
import { Badge } from "../../components/ui/badge";
|
|
15
13
|
import { Button } from "../../components/ui/button";
|
|
16
14
|
import { Skeleton } from "../../components/ui/skeleton";
|
|
15
|
+
import { Spinner } from "../../components/ui/spinner";
|
|
17
16
|
import {
|
|
18
17
|
workspaceAppHref,
|
|
19
18
|
type WorkspaceAppSummary,
|
|
20
19
|
} from "../../lib/workspace-apps";
|
|
21
20
|
|
|
21
|
+
interface EmbedSessionResult {
|
|
22
|
+
startUrl: string;
|
|
23
|
+
targetPath?: string;
|
|
24
|
+
expiresAt?: number;
|
|
25
|
+
app: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface EmbedSessionInput {
|
|
29
|
+
app: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
url?: string;
|
|
32
|
+
chrome: "minimal";
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
export function meta() {
|
|
23
36
|
return [{ title: "Workspace app - Dispatch" }];
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
export default function WorkspaceAppRoute() {
|
|
27
40
|
const t = useT();
|
|
28
|
-
const location = useLocation();
|
|
29
41
|
const { appId } = useParams();
|
|
30
42
|
const appsQuery = useActionQuery("list-workspace-apps", {
|
|
31
43
|
includeAgentCards: false,
|
|
@@ -37,41 +49,88 @@ export default function WorkspaceAppRoute() {
|
|
|
37
49
|
[appId, apps],
|
|
38
50
|
);
|
|
39
51
|
const href = app ? workspaceAppHref(app) : null;
|
|
52
|
+
const [embedUrl, setEmbedUrl] = useState<string | null>(null);
|
|
53
|
+
const [embedError, setEmbedError] = useState<Error | null>(null);
|
|
54
|
+
const [embedAttempt, setEmbedAttempt] = useState(0);
|
|
55
|
+
const createEmbedSession = useActionMutation<
|
|
56
|
+
EmbedSessionResult,
|
|
57
|
+
EmbedSessionInput
|
|
58
|
+
>("create_embed_session", {
|
|
59
|
+
skipActionQueryInvalidation: true,
|
|
60
|
+
});
|
|
61
|
+
const embedInput = useMemo<EmbedSessionInput | null>(() => {
|
|
62
|
+
if (!app || !href) return null;
|
|
63
|
+
const path = app.path.trim();
|
|
64
|
+
if (path.startsWith("/")) {
|
|
65
|
+
return { app: app.id, path, chrome: "minimal" };
|
|
66
|
+
}
|
|
67
|
+
return { app: app.id, url: href, chrome: "minimal" };
|
|
68
|
+
}, [app?.id, app?.path, href]);
|
|
40
69
|
|
|
41
70
|
useEffect(() => {
|
|
42
|
-
if (!app || app.status === "pending" || !
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
if (!app || app.status === "pending" || !embedInput) return;
|
|
72
|
+
let cancelled = false;
|
|
73
|
+
setEmbedUrl(null);
|
|
74
|
+
setEmbedError(null);
|
|
75
|
+
void createEmbedSession
|
|
76
|
+
.mutateAsync(embedInput)
|
|
77
|
+
.then((result) => {
|
|
78
|
+
if (!cancelled) setEmbedUrl(result.startUrl);
|
|
79
|
+
})
|
|
80
|
+
.catch((cause: unknown) => {
|
|
81
|
+
if (!cancelled) {
|
|
82
|
+
setEmbedError(
|
|
83
|
+
cause instanceof Error ? cause : new Error(String(cause)),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return () => {
|
|
88
|
+
cancelled = true;
|
|
89
|
+
};
|
|
90
|
+
}, [
|
|
91
|
+
app?.id,
|
|
92
|
+
app?.path,
|
|
93
|
+
app?.status,
|
|
94
|
+
createEmbedSession.mutateAsync,
|
|
95
|
+
embedAttempt,
|
|
96
|
+
embedInput,
|
|
97
|
+
]);
|
|
45
98
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
>
|
|
51
|
-
<div className="max-w-2xl rounded-lg bg-card p-5">
|
|
52
|
-
<Button asChild size="sm" variant="ghost" className="-ml-2 mb-4">
|
|
53
|
-
<Link
|
|
54
|
-
to={
|
|
55
|
-
location.pathname.startsWith("/admin/") ? "/admin/apps" : "/apps"
|
|
56
|
-
}
|
|
57
|
-
>
|
|
58
|
-
<IconArrowLeft size={15} className="mr-1.5" />
|
|
59
|
-
{t("dispatch.nav.apps")}
|
|
60
|
-
</Link>
|
|
61
|
-
</Button>
|
|
62
|
-
|
|
63
|
-
{appsQuery.isError ? (
|
|
99
|
+
if (appsQuery.isError) {
|
|
100
|
+
return (
|
|
101
|
+
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
102
|
+
<div className="w-full max-w-2xl">
|
|
64
103
|
<ActionQueryError
|
|
65
104
|
error={appsQuery.error}
|
|
66
105
|
onRetry={() => void appsQuery.refetch()}
|
|
67
106
|
/>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isLoading && !app) {
|
|
113
|
+
return (
|
|
114
|
+
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
115
|
+
<div className="w-full max-w-2xl space-y-3 rounded-xl border bg-card p-6">
|
|
116
|
+
<Skeleton className="h-5 w-48" />
|
|
117
|
+
<Skeleton className="h-4 w-full" />
|
|
118
|
+
<Skeleton className="h-4 w-2/3" />
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!app) {
|
|
125
|
+
return (
|
|
126
|
+
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
127
|
+
<div className="w-full max-w-2xl rounded-xl border bg-card p-6">
|
|
128
|
+
<Button asChild size="sm" variant="ghost" className="-ml-2 mb-4">
|
|
129
|
+
<Link to="/apps">
|
|
130
|
+
<IconArrowLeft size={15} className="mr-1.5" />
|
|
131
|
+
{t("dispatch.nav.apps")}
|
|
132
|
+
</Link>
|
|
133
|
+
</Button>
|
|
75
134
|
<div className="space-y-3">
|
|
76
135
|
<h2 className="text-base font-semibold text-foreground">
|
|
77
136
|
{t("dispatch.pages.appNotFound")}
|
|
@@ -80,7 +139,21 @@ export default function WorkspaceAppRoute() {
|
|
|
80
139
|
{t("dispatch.pages.pageNotFoundDescription")}
|
|
81
140
|
</p>
|
|
82
141
|
</div>
|
|
83
|
-
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (app.status === "pending") {
|
|
148
|
+
return (
|
|
149
|
+
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
150
|
+
<div className="w-full max-w-2xl rounded-xl border bg-card p-6">
|
|
151
|
+
<Button asChild size="sm" variant="ghost" className="-ml-2 mb-4">
|
|
152
|
+
<Link to="/apps">
|
|
153
|
+
<IconArrowLeft size={15} className="mr-1.5" />
|
|
154
|
+
{t("dispatch.nav.apps")}
|
|
155
|
+
</Link>
|
|
156
|
+
</Button>
|
|
84
157
|
<div className="space-y-4">
|
|
85
158
|
<div className="flex flex-wrap items-center gap-2">
|
|
86
159
|
<h2 className="text-base font-semibold text-foreground">
|
|
@@ -115,31 +188,45 @@ export default function WorkspaceAppRoute() {
|
|
|
115
188
|
rel="noreferrer"
|
|
116
189
|
>
|
|
117
190
|
{t("dispatch.pages.openBuilderBranch")}
|
|
118
|
-
<IconArrowUpRight size={15} className="ml-1.5" />
|
|
119
191
|
</a>
|
|
120
192
|
</Button>
|
|
121
193
|
) : null}
|
|
122
194
|
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div
|
|
202
|
+
data-dispatch-workspace-app-host
|
|
203
|
+
className="flex h-full min-h-0 flex-col bg-background"
|
|
204
|
+
>
|
|
205
|
+
<div className="min-h-0 flex-1 bg-muted/20">
|
|
206
|
+
{embedUrl ? (
|
|
207
|
+
<iframe
|
|
208
|
+
data-dispatch-workspace-app-frame
|
|
209
|
+
src={embedUrl}
|
|
210
|
+
title={app.name}
|
|
211
|
+
referrerPolicy="no-referrer"
|
|
212
|
+
className="h-full w-full border-0 bg-background"
|
|
213
|
+
/>
|
|
214
|
+
) : embedError ? (
|
|
215
|
+
<div className="flex h-full items-center justify-center p-6">
|
|
216
|
+
<div className="w-full max-w-2xl">
|
|
217
|
+
<ActionQueryError
|
|
218
|
+
error={embedError}
|
|
219
|
+
onRetry={() => setEmbedAttempt((attempt) => attempt + 1)}
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
123
223
|
) : (
|
|
124
|
-
<div className="
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
</h2>
|
|
128
|
-
<p className="text-sm text-muted-foreground">
|
|
129
|
-
{t("dispatch.pages.redirectingTo")}{" "}
|
|
130
|
-
<span className="font-mono text-foreground">{app.path}</span>.
|
|
131
|
-
</p>
|
|
132
|
-
{href ? (
|
|
133
|
-
<Button asChild>
|
|
134
|
-
<a href={href}>
|
|
135
|
-
{t("dispatch.pages.openApp")}
|
|
136
|
-
<IconArrowUpRight size={15} className="ml-1.5" />
|
|
137
|
-
</a>
|
|
138
|
-
</Button>
|
|
139
|
-
) : null}
|
|
224
|
+
<div className="flex h-full items-center justify-center">
|
|
225
|
+
<Spinner className="size-5 text-muted-foreground" />
|
|
226
|
+
<span className="sr-only">{t("dispatch.pages.loading")}</span>
|
|
140
227
|
</div>
|
|
141
228
|
)}
|
|
142
229
|
</div>
|
|
143
|
-
</
|
|
230
|
+
</div>
|
|
144
231
|
);
|
|
145
232
|
}
|
|
@@ -6,14 +6,19 @@ import {
|
|
|
6
6
|
IconClockHour4,
|
|
7
7
|
IconEyeOff,
|
|
8
8
|
IconPlus,
|
|
9
|
-
IconStack2,
|
|
10
9
|
} from "@tabler/icons-react";
|
|
11
10
|
import { useState } from "react";
|
|
11
|
+
import { Outlet, useParams } from "react-router";
|
|
12
12
|
|
|
13
13
|
import { ActionQueryError } from "../../components/action-query-error";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
APP_LIST_GRID_CLASS,
|
|
16
|
+
APP_LIST_GRID_ROW_CLASS,
|
|
17
|
+
AppList,
|
|
18
|
+
} from "../../components/app-list-row";
|
|
15
19
|
import { CreateAppPopover } from "../../components/create-app-popover";
|
|
16
20
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
21
|
+
import { OtherAppsSection } from "../../components/other-apps-section";
|
|
17
22
|
import { Button } from "../../components/ui/button";
|
|
18
23
|
import {
|
|
19
24
|
Collapsible,
|
|
@@ -22,15 +27,11 @@ import {
|
|
|
22
27
|
} from "../../components/ui/collapsible";
|
|
23
28
|
import { Skeleton } from "../../components/ui/skeleton";
|
|
24
29
|
import { WorkspaceAppCard } from "../../components/workspace-app-card";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
type WorkspaceTemplateLabels,
|
|
30
|
+
import type {
|
|
31
|
+
CuratedWorkspaceTemplatesResult,
|
|
32
|
+
WorkspaceTemplateLabels,
|
|
29
33
|
} from "../../components/workspace-template-card";
|
|
30
|
-
import {
|
|
31
|
-
filterOtherApps,
|
|
32
|
-
type ConnectedAppSummary,
|
|
33
|
-
} from "../../lib/other-apps";
|
|
34
|
+
import type { ConnectedAppSummary } from "../../lib/other-apps";
|
|
34
35
|
import { cn } from "../../lib/utils";
|
|
35
36
|
import type { WorkspaceAppSummary } from "../../lib/workspace-apps";
|
|
36
37
|
|
|
@@ -44,7 +45,12 @@ interface WorkspaceInfo {
|
|
|
44
45
|
appCount: number;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
export default function
|
|
48
|
+
export default function AppsRouteEntry() {
|
|
49
|
+
const { appId } = useParams();
|
|
50
|
+
return appId ? <Outlet /> : <AppsRoute />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function AppsRoute() {
|
|
48
54
|
const t = useT();
|
|
49
55
|
const [showPending, setShowPending] = useState(false);
|
|
50
56
|
const [showHidden, setShowHidden] = useState(false);
|
|
@@ -72,10 +78,6 @@ export default function AppsRoute() {
|
|
|
72
78
|
const activeApps = visibleApps.filter((app) => app.status !== "pending");
|
|
73
79
|
const pendingApps = visibleApps.filter((app) => app.status === "pending");
|
|
74
80
|
const archivedApps = allApps.filter((app) => app.archived);
|
|
75
|
-
const otherApps = filterOtherApps(
|
|
76
|
-
(connectedAppsQuery.data || []) as ConnectedAppSummary[],
|
|
77
|
-
allApps,
|
|
78
|
-
);
|
|
79
81
|
const showAppSkeletons = appsLoading && allApps.length === 0;
|
|
80
82
|
const templateLabels: WorkspaceTemplateLabels = {
|
|
81
83
|
appId: t("dispatch.pages.remixAppIdLabel"),
|
|
@@ -83,13 +85,14 @@ export default function AppsRoute() {
|
|
|
83
85
|
cancel: t("dispatch.pages.cancel"),
|
|
84
86
|
integrationSetup: t("dispatch.pages.integrationSetup"),
|
|
85
87
|
installed: t("dispatch.pages.alreadyInWorkspace"),
|
|
86
|
-
remix: t("dispatch.pages.
|
|
88
|
+
remix: t("dispatch.pages.addApp", { defaultValue: "Add app" }),
|
|
87
89
|
remixing: t("dispatch.pages.remixing"),
|
|
88
90
|
remixSuccess: t("dispatch.pages.remixSuccess"),
|
|
89
91
|
remixError: t("dispatch.pages.remixError"),
|
|
90
92
|
appIdRequired: t("dispatch.pages.appIdRequired"),
|
|
91
93
|
source: t("dispatch.pages.source"),
|
|
92
|
-
|
|
94
|
+
openApp: t("dispatch.pages.openApp", { defaultValue: "Open app" }),
|
|
95
|
+
viewLiveApp: t("dispatch.pages.openApp", { defaultValue: "Open" }),
|
|
93
96
|
};
|
|
94
97
|
|
|
95
98
|
return (
|
|
@@ -113,11 +116,7 @@ export default function AppsRoute() {
|
|
|
113
116
|
/>
|
|
114
117
|
<div className="min-w-0">
|
|
115
118
|
<h2 className="truncate text-sm font-semibold text-foreground">
|
|
116
|
-
{
|
|
117
|
-
? t("dispatch.pages.appsInWorkspace", {
|
|
118
|
-
workspace: workspaceLabel,
|
|
119
|
-
})
|
|
120
|
-
: t("dispatch.pages.workspaceApps")}
|
|
119
|
+
{t("dispatch.pages.yourApps", { defaultValue: "Your apps" })}
|
|
121
120
|
</h2>
|
|
122
121
|
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
123
122
|
{t("dispatch.pages.activeCount", {
|
|
@@ -140,9 +139,9 @@ export default function AppsRoute() {
|
|
|
140
139
|
<CreateAppPopover
|
|
141
140
|
align="end"
|
|
142
141
|
trigger={
|
|
143
|
-
<Button size="sm">
|
|
142
|
+
<Button size="sm" variant="outline">
|
|
144
143
|
<IconPlus size={15} className="mr-1.5" />
|
|
145
|
-
{t("dispatch.pages.
|
|
144
|
+
{t("dispatch.pages.addApp", { defaultValue: "Add app" })}
|
|
146
145
|
</Button>
|
|
147
146
|
}
|
|
148
147
|
/>
|
|
@@ -157,11 +156,15 @@ export default function AppsRoute() {
|
|
|
157
156
|
) : showAppSkeletons ? (
|
|
158
157
|
<AppsSkeletonGrid />
|
|
159
158
|
) : activeApps.length > 0 ? (
|
|
160
|
-
<
|
|
159
|
+
<AppList className={APP_LIST_GRID_CLASS}>
|
|
161
160
|
{activeApps.map((app) => (
|
|
162
|
-
<WorkspaceAppCard
|
|
161
|
+
<WorkspaceAppCard
|
|
162
|
+
key={app.id}
|
|
163
|
+
app={app}
|
|
164
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
165
|
+
/>
|
|
163
166
|
))}
|
|
164
|
-
</
|
|
167
|
+
</AppList>
|
|
165
168
|
) : pendingApps.length > 0 ? (
|
|
166
169
|
<EmptyActiveAppsState />
|
|
167
170
|
) : (
|
|
@@ -171,7 +174,7 @@ export default function AppsRoute() {
|
|
|
171
174
|
|
|
172
175
|
{pendingApps.length > 0 ? (
|
|
173
176
|
<Collapsible open={showPending} onOpenChange={setShowPending}>
|
|
174
|
-
<section className="space-y-3 border-t pt-
|
|
177
|
+
<section className="space-y-3 border-t pt-6">
|
|
175
178
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
176
179
|
<div className="flex min-w-0 items-center gap-2">
|
|
177
180
|
<IconClockHour4
|
|
@@ -183,7 +186,7 @@ export default function AppsRoute() {
|
|
|
183
186
|
{t("dispatch.pages.pendingApps")}
|
|
184
187
|
</h2>
|
|
185
188
|
<p className="text-xs text-muted-foreground">
|
|
186
|
-
{t("dispatch.pages.
|
|
189
|
+
{t("dispatch.pages.pendingCount", {
|
|
187
190
|
count: pendingApps.length,
|
|
188
191
|
})}
|
|
189
192
|
</p>
|
|
@@ -210,78 +213,42 @@ export default function AppsRoute() {
|
|
|
210
213
|
</CollapsibleTrigger>
|
|
211
214
|
</div>
|
|
212
215
|
<CollapsibleContent>
|
|
213
|
-
<
|
|
216
|
+
<AppList className={APP_LIST_GRID_CLASS}>
|
|
214
217
|
{pendingApps.map((app) => (
|
|
215
218
|
<WorkspaceAppCard
|
|
216
219
|
key={app.id}
|
|
217
220
|
app={app}
|
|
218
|
-
className=
|
|
221
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
219
222
|
/>
|
|
220
223
|
))}
|
|
221
|
-
</
|
|
224
|
+
</AppList>
|
|
222
225
|
</CollapsibleContent>
|
|
223
226
|
</section>
|
|
224
227
|
</Collapsible>
|
|
225
228
|
) : null}
|
|
226
229
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
</div>
|
|
250
|
-
<WorkspaceTemplatesSection
|
|
251
|
-
templates={
|
|
252
|
-
curatedTemplatesQuery.data as CuratedWorkspaceTemplatesResult
|
|
253
|
-
}
|
|
254
|
-
labels={templateLabels}
|
|
255
|
-
onRemixSuccess={() => {
|
|
256
|
-
void appsQuery.refetch();
|
|
257
|
-
void curatedTemplatesQuery.refetch();
|
|
258
|
-
}}
|
|
259
|
-
/>
|
|
260
|
-
</section>
|
|
261
|
-
) : null}
|
|
262
|
-
|
|
263
|
-
{connectedAppsQuery.isError ? (
|
|
264
|
-
<section className="space-y-3 border-t pt-4">
|
|
265
|
-
<OtherAppsHeading count={0} />
|
|
266
|
-
<ActionQueryError
|
|
267
|
-
error={connectedAppsQuery.error}
|
|
268
|
-
onRetry={() => void connectedAppsQuery.refetch()}
|
|
269
|
-
/>
|
|
270
|
-
</section>
|
|
271
|
-
) : connectedAppsQuery.isLoading || otherApps.length > 0 ? (
|
|
272
|
-
<section className="space-y-3 border-t pt-4">
|
|
273
|
-
<OtherAppsHeading count={otherApps.length} />
|
|
274
|
-
{connectedAppsQuery.isLoading ? (
|
|
275
|
-
<OtherAppsSkeletonGrid />
|
|
276
|
-
) : (
|
|
277
|
-
<div className="grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
278
|
-
{otherApps.map((app) => (
|
|
279
|
-
<ConnectedAppCard key={app.id} app={app} />
|
|
280
|
-
))}
|
|
281
|
-
</div>
|
|
282
|
-
)}
|
|
283
|
-
</section>
|
|
284
|
-
) : null}
|
|
230
|
+
<OtherAppsSection
|
|
231
|
+
templates={
|
|
232
|
+
curatedTemplatesQuery.data as
|
|
233
|
+
| CuratedWorkspaceTemplatesResult
|
|
234
|
+
| undefined
|
|
235
|
+
}
|
|
236
|
+
connectedApps={
|
|
237
|
+
connectedAppsQuery.data as ConnectedAppSummary[] | undefined
|
|
238
|
+
}
|
|
239
|
+
workspaceApps={allApps}
|
|
240
|
+
templatesLoading={curatedTemplatesQuery.isLoading}
|
|
241
|
+
connectedAppsLoading={connectedAppsQuery.isLoading}
|
|
242
|
+
templatesError={curatedTemplatesQuery.error}
|
|
243
|
+
connectedAppsError={connectedAppsQuery.error}
|
|
244
|
+
onRetryTemplates={() => void curatedTemplatesQuery.refetch()}
|
|
245
|
+
onRetryConnectedApps={() => void connectedAppsQuery.refetch()}
|
|
246
|
+
templateLabels={templateLabels}
|
|
247
|
+
onRemixSuccess={() => {
|
|
248
|
+
void appsQuery.refetch();
|
|
249
|
+
void curatedTemplatesQuery.refetch();
|
|
250
|
+
}}
|
|
251
|
+
/>
|
|
285
252
|
|
|
286
253
|
{archivedApps.length > 0 ? (
|
|
287
254
|
<Collapsible open={showHidden} onOpenChange={setShowHidden}>
|
|
@@ -324,15 +291,15 @@ export default function AppsRoute() {
|
|
|
324
291
|
</CollapsibleTrigger>
|
|
325
292
|
</div>
|
|
326
293
|
<CollapsibleContent>
|
|
327
|
-
<
|
|
294
|
+
<AppList className={APP_LIST_GRID_CLASS}>
|
|
328
295
|
{archivedApps.map((app) => (
|
|
329
296
|
<WorkspaceAppCard
|
|
330
297
|
key={app.id}
|
|
331
298
|
app={app}
|
|
332
|
-
className=
|
|
299
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
333
300
|
/>
|
|
334
301
|
))}
|
|
335
|
-
</
|
|
302
|
+
</AppList>
|
|
336
303
|
</CollapsibleContent>
|
|
337
304
|
</section>
|
|
338
305
|
</Collapsible>
|
|
@@ -342,64 +309,26 @@ export default function AppsRoute() {
|
|
|
342
309
|
);
|
|
343
310
|
}
|
|
344
311
|
|
|
345
|
-
function OtherAppsHeading({ count }: { count: number }) {
|
|
346
|
-
const t = useT();
|
|
347
|
-
return (
|
|
348
|
-
<div className="flex min-w-0 items-start gap-2">
|
|
349
|
-
<IconStack2 size={16} className="mt-0.5 shrink-0 text-muted-foreground" />
|
|
350
|
-
<div className="min-w-0">
|
|
351
|
-
<h2 className="truncate text-sm font-semibold text-foreground">
|
|
352
|
-
{t("dispatch.pages.otherApps")}
|
|
353
|
-
</h2>
|
|
354
|
-
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
355
|
-
{t("dispatch.pages.otherAppsDescription")}
|
|
356
|
-
{count > 0
|
|
357
|
-
? ` · ${t("dispatch.pages.availableCount", { count })}`
|
|
358
|
-
: ""}
|
|
359
|
-
</p>
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function OtherAppsSkeletonGrid() {
|
|
366
|
-
return (
|
|
367
|
-
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
368
|
-
{Array.from({ length: 3 }).map((_, index) => (
|
|
369
|
-
<div key={index} className="rounded-lg bg-card p-4">
|
|
370
|
-
<div className="flex items-start gap-3">
|
|
371
|
-
<Skeleton className="size-8 rounded-md" />
|
|
372
|
-
<div className="flex-1 space-y-3">
|
|
373
|
-
<Skeleton className="h-4 w-32" />
|
|
374
|
-
<Skeleton className="h-3 w-full" />
|
|
375
|
-
<Skeleton className="h-3 w-2/3" />
|
|
376
|
-
</div>
|
|
377
|
-
</div>
|
|
378
|
-
</div>
|
|
379
|
-
))}
|
|
380
|
-
</div>
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
312
|
function AppsSkeletonGrid() {
|
|
385
313
|
return (
|
|
386
|
-
<
|
|
314
|
+
<AppList className={APP_LIST_GRID_CLASS}>
|
|
387
315
|
{Array.from({ length: 3 }).map((_, index) => (
|
|
388
|
-
<div
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
<Skeleton className="h-
|
|
316
|
+
<div
|
|
317
|
+
key={index}
|
|
318
|
+
className={cn(
|
|
319
|
+
"flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0",
|
|
320
|
+
APP_LIST_GRID_ROW_CLASS,
|
|
321
|
+
)}
|
|
322
|
+
>
|
|
323
|
+
<Skeleton className="size-8 shrink-0 rounded-md" />
|
|
324
|
+
<div className="min-w-0 flex-1 space-y-2">
|
|
325
|
+
<Skeleton className="h-4 w-32" />
|
|
326
|
+
<Skeleton className="h-3 w-2/3" />
|
|
399
327
|
</div>
|
|
328
|
+
<Skeleton className="h-8 w-24 shrink-0 rounded-md" />
|
|
400
329
|
</div>
|
|
401
330
|
))}
|
|
402
|
-
</
|
|
331
|
+
</AppList>
|
|
403
332
|
);
|
|
404
333
|
}
|
|
405
334
|
|
|
@@ -419,9 +348,9 @@ function EmptyAppsState() {
|
|
|
419
348
|
<div className="mt-4">
|
|
420
349
|
<CreateAppPopover
|
|
421
350
|
trigger={
|
|
422
|
-
<Button size="sm">
|
|
351
|
+
<Button size="sm" variant="outline">
|
|
423
352
|
<IconPlus size={15} className="mr-1.5" />
|
|
424
|
-
{t("dispatch.pages.
|
|
353
|
+
{t("dispatch.pages.addApp", { defaultValue: "Add app" })}
|
|
425
354
|
</Button>
|
|
426
355
|
}
|
|
427
356
|
/>
|
|
@@ -24,6 +24,11 @@ describe("Dispatch agent chat plugin", () => {
|
|
|
24
24
|
expect.objectContaining({
|
|
25
25
|
appId: "dispatch",
|
|
26
26
|
durableBackgroundRuns: true,
|
|
27
|
+
mcp: {
|
|
28
|
+
connectorCatalog: expect.arrayContaining([
|
|
29
|
+
"start-workspace-app-creation",
|
|
30
|
+
]),
|
|
31
|
+
},
|
|
27
32
|
}),
|
|
28
33
|
);
|
|
29
34
|
});
|
|
@@ -31,7 +31,12 @@ export default createAgentChatPlugin({
|
|
|
31
31
|
appId: "dispatch",
|
|
32
32
|
durableBackgroundRuns: true,
|
|
33
33
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
34
|
-
mcp: {
|
|
34
|
+
mcp: {
|
|
35
|
+
connectorCatalog: [
|
|
36
|
+
"resolve-integration-source-context",
|
|
37
|
+
"start-workspace-app-creation",
|
|
38
|
+
],
|
|
39
|
+
},
|
|
35
40
|
// Without this, AGENT_ORG_ID is never set on agent action calls and every
|
|
36
41
|
// row written through the frontend (vault secrets, destinations, workspace
|
|
37
42
|
// resources) lands with org_id=NULL — breaking data isolation across orgs.
|