@agent-native/dispatch 0.25.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/provider-api-register.d.ts +14 -14
- package/dist/components/create-app-popover.d.ts +7 -2
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +4 -3
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +42 -2
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/simple-agents-panel.d.ts +4 -1
- package/dist/components/simple-agents-panel.d.ts.map +1 -1
- package/dist/components/simple-agents-panel.js +46 -13
- package/dist/components/simple-agents-panel.js.map +1 -1
- package/dist/components/workspace-app-host.d.ts +3 -1
- package/dist/components/workspace-app-host.d.ts.map +1 -1
- package/dist/components/workspace-app-host.js +87 -5
- package/dist/components/workspace-app-host.js.map +1 -1
- package/package.json +3 -3
- package/src/components/create-app-popover.spec.tsx +9 -1
- package/src/components/create-app-popover.tsx +9 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/Layout.app-chat.spec.ts +22 -0
- package/src/components/layout/Layout.tsx +72 -3
- package/src/components/simple-agents-panel.spec.tsx +81 -1
- package/src/components/simple-agents-panel.tsx +156 -71
- package/src/components/workspace-app-host.spec.tsx +57 -0
- package/src/components/workspace-app-host.tsx +139 -3
|
@@ -10,6 +10,7 @@ import { parseCustomAgentProfile } from "@agent-native/core/resources/metadata";
|
|
|
10
10
|
import {
|
|
11
11
|
IconAdjustmentsHorizontal,
|
|
12
12
|
IconChevronDown,
|
|
13
|
+
IconDotsVertical,
|
|
13
14
|
IconEdit,
|
|
14
15
|
IconFileImport,
|
|
15
16
|
IconFolder,
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
IconPlus,
|
|
20
21
|
IconTrash,
|
|
21
22
|
IconUpload,
|
|
22
|
-
IconUser,
|
|
23
23
|
} from "@tabler/icons-react";
|
|
24
24
|
import {
|
|
25
25
|
useEffect,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
slugifyAgentName,
|
|
37
37
|
} from "../lib/simple-agent-profile.js";
|
|
38
38
|
import { ActionQueryError } from "./action-query-error";
|
|
39
|
+
import { AppIcon } from "./app-icon";
|
|
39
40
|
import {
|
|
40
41
|
AlertDialog,
|
|
41
42
|
AlertDialogAction,
|
|
@@ -63,6 +64,13 @@ import {
|
|
|
63
64
|
DialogTitle,
|
|
64
65
|
DialogTrigger,
|
|
65
66
|
} from "./ui/dialog";
|
|
67
|
+
import {
|
|
68
|
+
DropdownMenu,
|
|
69
|
+
DropdownMenuContent,
|
|
70
|
+
DropdownMenuItem,
|
|
71
|
+
DropdownMenuSeparator,
|
|
72
|
+
DropdownMenuTrigger,
|
|
73
|
+
} from "./ui/dropdown-menu";
|
|
66
74
|
import { Input } from "./ui/input";
|
|
67
75
|
import { Label } from "./ui/label";
|
|
68
76
|
import {
|
|
@@ -101,6 +109,25 @@ interface AgentPackFileInput {
|
|
|
101
109
|
content: string;
|
|
102
110
|
}
|
|
103
111
|
|
|
112
|
+
const AGENT_ICON_KEYS = [
|
|
113
|
+
"brain",
|
|
114
|
+
"users",
|
|
115
|
+
"filetext",
|
|
116
|
+
"chartbar",
|
|
117
|
+
"route",
|
|
118
|
+
"listcheck",
|
|
119
|
+
"code",
|
|
120
|
+
"messagecircle",
|
|
121
|
+
] as const;
|
|
122
|
+
|
|
123
|
+
function agentIconKey(resource: Pick<WorkspaceAgentResource, "id" | "name">) {
|
|
124
|
+
let hash = 0;
|
|
125
|
+
for (const character of `${resource.id}:${resource.name}`) {
|
|
126
|
+
hash = (hash * 31 + character.charCodeAt(0)) | 0;
|
|
127
|
+
}
|
|
128
|
+
return AGENT_ICON_KEYS[Math.abs(hash) % AGENT_ICON_KEYS.length];
|
|
129
|
+
}
|
|
130
|
+
|
|
104
131
|
export function isPendingWorkspaceResourceApproval(result: unknown) {
|
|
105
132
|
if (!result || typeof result !== "object") return false;
|
|
106
133
|
const mutation = result as { status?: unknown; changeType?: unknown };
|
|
@@ -133,6 +160,8 @@ interface AgentEditorProps {
|
|
|
133
160
|
resource?: WorkspaceAgentResource;
|
|
134
161
|
trigger?: ReactNode;
|
|
135
162
|
onSaved?: () => void;
|
|
163
|
+
open?: boolean;
|
|
164
|
+
onOpenChange?: (open: boolean) => void;
|
|
136
165
|
}
|
|
137
166
|
|
|
138
167
|
function profileFields(resource?: WorkspaceAgentResource) {
|
|
@@ -158,9 +187,17 @@ function profileFields(resource?: WorkspaceAgentResource) {
|
|
|
158
187
|
};
|
|
159
188
|
}
|
|
160
189
|
|
|
161
|
-
function AgentEditorDialog({
|
|
190
|
+
function AgentEditorDialog({
|
|
191
|
+
resource,
|
|
192
|
+
trigger,
|
|
193
|
+
onSaved,
|
|
194
|
+
open: controlledOpen,
|
|
195
|
+
onOpenChange: controlledOnOpenChange,
|
|
196
|
+
}: AgentEditorProps) {
|
|
162
197
|
const isEditing = Boolean(resource);
|
|
163
|
-
const [
|
|
198
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
|
|
199
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
200
|
+
const setOpen = controlledOnOpenChange ?? setUncontrolledOpen;
|
|
164
201
|
const [name, setName] = useState("");
|
|
165
202
|
const [description, setDescription] = useState("");
|
|
166
203
|
const [instructions, setInstructions] = useState("");
|
|
@@ -231,14 +268,16 @@ function AgentEditorDialog({ resource, trigger, onSaved }: AgentEditorProps) {
|
|
|
231
268
|
|
|
232
269
|
return (
|
|
233
270
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
234
|
-
|
|
235
|
-
{trigger
|
|
236
|
-
|
|
271
|
+
{trigger ? (
|
|
272
|
+
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
|
273
|
+
) : controlledOpen === undefined ? (
|
|
274
|
+
<DialogTrigger asChild>
|
|
275
|
+
<Button size="sm">
|
|
237
276
|
<IconPlus size={16} />
|
|
238
277
|
Create agent
|
|
239
278
|
</Button>
|
|
240
|
-
|
|
241
|
-
|
|
279
|
+
</DialogTrigger>
|
|
280
|
+
) : null}
|
|
242
281
|
<DialogContent className="max-w-2xl">
|
|
243
282
|
<DialogHeader>
|
|
244
283
|
<DialogTitle>
|
|
@@ -355,12 +394,18 @@ function AgentPackDialog({
|
|
|
355
394
|
resource,
|
|
356
395
|
onChanged,
|
|
357
396
|
trigger,
|
|
397
|
+
open: controlledOpen,
|
|
398
|
+
onOpenChange: controlledOnOpenChange,
|
|
358
399
|
}: {
|
|
359
400
|
resource: WorkspaceAgentResource;
|
|
360
401
|
onChanged?: () => void;
|
|
361
402
|
trigger?: ReactNode;
|
|
403
|
+
open?: boolean;
|
|
404
|
+
onOpenChange?: (open: boolean) => void;
|
|
362
405
|
}) {
|
|
363
|
-
const [
|
|
406
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
|
|
407
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
408
|
+
const setOpen = controlledOnOpenChange ?? setUncontrolledOpen;
|
|
364
409
|
const [selectedId, setSelectedId] = useState(resource.id);
|
|
365
410
|
const [content, setContent] = useState(resource.content);
|
|
366
411
|
const [addOpen, setAddOpen] = useState(false);
|
|
@@ -460,14 +505,16 @@ function AgentPackDialog({
|
|
|
460
505
|
if (!nextOpen) setAddOpen(false);
|
|
461
506
|
}}
|
|
462
507
|
>
|
|
463
|
-
|
|
464
|
-
{trigger
|
|
508
|
+
{trigger ? (
|
|
509
|
+
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
|
510
|
+
) : controlledOpen === undefined ? (
|
|
511
|
+
<DialogTrigger asChild>
|
|
465
512
|
<Button variant="ghost" size="sm">
|
|
466
513
|
<IconFolder size={15} />
|
|
467
514
|
Pack
|
|
468
515
|
</Button>
|
|
469
|
-
|
|
470
|
-
|
|
516
|
+
</DialogTrigger>
|
|
517
|
+
) : null}
|
|
471
518
|
<DialogContent className="max-w-4xl">
|
|
472
519
|
<DialogHeader>
|
|
473
520
|
<DialogTitle>Agent pack</DialogTitle>
|
|
@@ -753,7 +800,7 @@ function ImportAgentDialog({ onImported }: { onImported?: () => void }) {
|
|
|
753
800
|
}}
|
|
754
801
|
>
|
|
755
802
|
<DialogTrigger asChild>
|
|
756
|
-
<Button variant="outline">
|
|
803
|
+
<Button variant="outline" size="sm">
|
|
757
804
|
<IconFileImport size={16} />
|
|
758
805
|
Import or connect
|
|
759
806
|
</Button>
|
|
@@ -957,10 +1004,17 @@ function ImportAgentDialog({ onImported }: { onImported?: () => void }) {
|
|
|
957
1004
|
function DeleteAgentButton({
|
|
958
1005
|
resource,
|
|
959
1006
|
onDeleted,
|
|
1007
|
+
open: controlledOpen,
|
|
1008
|
+
onOpenChange: controlledOnOpenChange,
|
|
960
1009
|
}: {
|
|
961
1010
|
resource: WorkspaceAgentResource;
|
|
962
1011
|
onDeleted?: () => void;
|
|
1012
|
+
open?: boolean;
|
|
1013
|
+
onOpenChange?: (open: boolean) => void;
|
|
963
1014
|
}) {
|
|
1015
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
|
|
1016
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
1017
|
+
const setOpen = controlledOnOpenChange ?? setUncontrolledOpen;
|
|
964
1018
|
const remove = useActionMutation("delete-workspace-resource", {
|
|
965
1019
|
onSuccess: () => {
|
|
966
1020
|
toast.success("Agent removed");
|
|
@@ -970,16 +1024,7 @@ function DeleteAgentButton({
|
|
|
970
1024
|
});
|
|
971
1025
|
|
|
972
1026
|
return (
|
|
973
|
-
<AlertDialog>
|
|
974
|
-
<AlertDialogTrigger asChild>
|
|
975
|
-
<Button
|
|
976
|
-
variant="ghost"
|
|
977
|
-
size="icon"
|
|
978
|
-
aria-label={`Remove ${resource.name}`}
|
|
979
|
-
>
|
|
980
|
-
<IconTrash size={16} />
|
|
981
|
-
</Button>
|
|
982
|
-
</AlertDialogTrigger>
|
|
1027
|
+
<AlertDialog open={open} onOpenChange={setOpen}>
|
|
983
1028
|
<AlertDialogContent>
|
|
984
1029
|
<AlertDialogHeader>
|
|
985
1030
|
<AlertDialogTitle>Remove {resource.name}?</AlertDialogTitle>
|
|
@@ -1011,6 +1056,9 @@ function AgentRow({
|
|
|
1011
1056
|
onSaved?: () => void;
|
|
1012
1057
|
}) {
|
|
1013
1058
|
const navigate = useNavigate();
|
|
1059
|
+
const [packOpen, setPackOpen] = useState(false);
|
|
1060
|
+
const [editorOpen, setEditorOpen] = useState(false);
|
|
1061
|
+
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
1014
1062
|
const packQuery = useActionQuery<AgentPackResponse>(
|
|
1015
1063
|
"list-agent-pack",
|
|
1016
1064
|
{ agentId: resource.id },
|
|
@@ -1072,10 +1120,13 @@ function AgentRow({
|
|
|
1072
1120
|
}
|
|
1073
1121
|
|
|
1074
1122
|
return (
|
|
1075
|
-
<div className="flex items-start gap-3 rounded-
|
|
1076
|
-
<
|
|
1077
|
-
|
|
1078
|
-
|
|
1123
|
+
<div className="flex min-w-0 items-start gap-3 rounded-2xl border bg-card px-4 py-4">
|
|
1124
|
+
<AppIcon
|
|
1125
|
+
id={resource.id}
|
|
1126
|
+
name={resource.name}
|
|
1127
|
+
icon={agentIconKey(resource)}
|
|
1128
|
+
size="sm"
|
|
1129
|
+
/>
|
|
1079
1130
|
<div className="min-w-0 flex-1">
|
|
1080
1131
|
<div className="flex flex-wrap items-center gap-2">
|
|
1081
1132
|
<span className="text-sm font-medium text-foreground">
|
|
@@ -1090,11 +1141,8 @@ function AgentRow({
|
|
|
1090
1141
|
{resource.description}
|
|
1091
1142
|
</div>
|
|
1092
1143
|
) : null}
|
|
1093
|
-
<div className="mt-1 font-mono text-[11px] text-muted-foreground/70">
|
|
1094
|
-
{resource.path}
|
|
1095
|
-
</div>
|
|
1096
1144
|
</div>
|
|
1097
|
-
<div className="flex shrink-0
|
|
1145
|
+
<div className="flex shrink-0 items-center gap-1">
|
|
1098
1146
|
<Button
|
|
1099
1147
|
variant="outline"
|
|
1100
1148
|
size="sm"
|
|
@@ -1104,43 +1152,63 @@ function AgentRow({
|
|
|
1104
1152
|
<IconMessageCircle size={15} />
|
|
1105
1153
|
Chat
|
|
1106
1154
|
</Button>
|
|
1107
|
-
<
|
|
1108
|
-
|
|
1109
|
-
size="sm"
|
|
1110
|
-
onClick={() => void buildApp()}
|
|
1111
|
-
disabled={promote.isPending}
|
|
1112
|
-
aria-label={`Build an app for ${resource.name}`}
|
|
1113
|
-
>
|
|
1114
|
-
<IconLayoutGrid size={15} />
|
|
1115
|
-
{promote.isPending ? "Starting..." : "Build app"}
|
|
1116
|
-
</Button>
|
|
1117
|
-
<AgentPackDialog
|
|
1118
|
-
resource={resource}
|
|
1119
|
-
onChanged={onSaved}
|
|
1120
|
-
trigger={
|
|
1155
|
+
<DropdownMenu>
|
|
1156
|
+
<DropdownMenuTrigger asChild>
|
|
1121
1157
|
<Button
|
|
1122
1158
|
variant="ghost"
|
|
1123
1159
|
size="icon"
|
|
1124
|
-
aria-label={`
|
|
1160
|
+
aria-label={`More actions for ${resource.name}`}
|
|
1161
|
+
title="More actions"
|
|
1125
1162
|
>
|
|
1126
|
-
<
|
|
1163
|
+
<IconDotsVertical size={16} />
|
|
1127
1164
|
</Button>
|
|
1128
|
-
|
|
1165
|
+
</DropdownMenuTrigger>
|
|
1166
|
+
<DropdownMenuContent align="end" className="w-48">
|
|
1167
|
+
<DropdownMenuItem
|
|
1168
|
+
onSelect={() => {
|
|
1169
|
+
void buildApp();
|
|
1170
|
+
}}
|
|
1171
|
+
disabled={promote.isPending}
|
|
1172
|
+
>
|
|
1173
|
+
<IconLayoutGrid className="me-2 size-4" />
|
|
1174
|
+
{promote.isPending ? "Starting..." : "Build app"}
|
|
1175
|
+
</DropdownMenuItem>
|
|
1176
|
+
<DropdownMenuItem onSelect={() => setPackOpen(true)}>
|
|
1177
|
+
<IconFolder className="me-2 size-4" />
|
|
1178
|
+
Manage files
|
|
1179
|
+
</DropdownMenuItem>
|
|
1180
|
+
<DropdownMenuItem onSelect={() => setEditorOpen(true)}>
|
|
1181
|
+
<IconEdit className="me-2 size-4" />
|
|
1182
|
+
Edit
|
|
1183
|
+
</DropdownMenuItem>
|
|
1184
|
+
<DropdownMenuSeparator />
|
|
1185
|
+
<DropdownMenuItem
|
|
1186
|
+
className="text-destructive focus:text-destructive"
|
|
1187
|
+
onSelect={() => setDeleteOpen(true)}
|
|
1188
|
+
>
|
|
1189
|
+
<IconTrash className="me-2 size-4" />
|
|
1190
|
+
Remove
|
|
1191
|
+
</DropdownMenuItem>
|
|
1192
|
+
</DropdownMenuContent>
|
|
1193
|
+
</DropdownMenu>
|
|
1194
|
+
<AgentPackDialog
|
|
1195
|
+
resource={resource}
|
|
1196
|
+
onChanged={onSaved}
|
|
1197
|
+
open={packOpen}
|
|
1198
|
+
onOpenChange={setPackOpen}
|
|
1129
1199
|
/>
|
|
1130
1200
|
<AgentEditorDialog
|
|
1131
1201
|
resource={resource}
|
|
1132
1202
|
onSaved={onSaved}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1203
|
+
open={editorOpen}
|
|
1204
|
+
onOpenChange={setEditorOpen}
|
|
1205
|
+
/>
|
|
1206
|
+
<DeleteAgentButton
|
|
1207
|
+
resource={resource}
|
|
1208
|
+
onDeleted={onDeleted}
|
|
1209
|
+
open={deleteOpen}
|
|
1210
|
+
onOpenChange={setDeleteOpen}
|
|
1142
1211
|
/>
|
|
1143
|
-
<DeleteAgentButton resource={resource} onDeleted={onDeleted} />
|
|
1144
1212
|
</div>
|
|
1145
1213
|
</div>
|
|
1146
1214
|
);
|
|
@@ -1160,12 +1228,18 @@ interface AgentAppCreationResult {
|
|
|
1160
1228
|
url?: string;
|
|
1161
1229
|
}
|
|
1162
1230
|
|
|
1163
|
-
export function SimpleAgentsPanel(
|
|
1231
|
+
export function SimpleAgentsPanel({
|
|
1232
|
+
title,
|
|
1233
|
+
}: {
|
|
1234
|
+
title?: ReactNode;
|
|
1235
|
+
} = {}) {
|
|
1164
1236
|
const query = useActionQuery<WorkspaceAgentResource[]>(
|
|
1165
1237
|
"list-workspace-resources",
|
|
1166
1238
|
{ kind: "agent" },
|
|
1167
1239
|
);
|
|
1168
1240
|
const agents = query.data ?? [];
|
|
1241
|
+
const refreshAgents = () => void query.refetch();
|
|
1242
|
+
const importAction = <ImportAgentDialog onImported={refreshAgents} />;
|
|
1169
1243
|
|
|
1170
1244
|
if (query.isError) {
|
|
1171
1245
|
return (
|
|
@@ -1178,21 +1252,32 @@ export function SimpleAgentsPanel() {
|
|
|
1178
1252
|
|
|
1179
1253
|
return (
|
|
1180
1254
|
<section className="flex flex-col gap-4">
|
|
1181
|
-
|
|
1182
|
-
<
|
|
1183
|
-
|
|
1184
|
-
|
|
1255
|
+
{title || agents.length > 0 ? (
|
|
1256
|
+
<div
|
|
1257
|
+
className={`flex flex-wrap items-center gap-3 ${title ? "justify-between" : "justify-end"}`}
|
|
1258
|
+
>
|
|
1259
|
+
{title ? (
|
|
1260
|
+
<h2 className="text-base font-medium text-foreground">{title}</h2>
|
|
1261
|
+
) : null}
|
|
1262
|
+
{agents.length > 0 ? (
|
|
1263
|
+
<div className="flex flex-wrap items-center justify-end gap-2">
|
|
1264
|
+
{importAction}
|
|
1265
|
+
<AgentEditorDialog onSaved={refreshAgents} />
|
|
1266
|
+
</div>
|
|
1267
|
+
) : null}
|
|
1268
|
+
</div>
|
|
1269
|
+
) : null}
|
|
1185
1270
|
{query.isLoading && agents.length === 0 ? (
|
|
1186
|
-
<div className="
|
|
1271
|
+
<div className="grid gap-3 md:grid-cols-2">
|
|
1187
1272
|
{[0, 1, 2].map((item) => (
|
|
1188
|
-
<div key={item} className="rounded-
|
|
1273
|
+
<div key={item} className="rounded-2xl border bg-card px-4 py-4">
|
|
1189
1274
|
<Skeleton className="h-4 w-1/3" />
|
|
1190
1275
|
<Skeleton className="mt-2 h-3 w-2/3" />
|
|
1191
1276
|
</div>
|
|
1192
1277
|
))}
|
|
1193
1278
|
</div>
|
|
1194
1279
|
) : agents.length > 0 ? (
|
|
1195
|
-
<div className="
|
|
1280
|
+
<div className="grid gap-3 md:grid-cols-2">
|
|
1196
1281
|
{agents.map((agent) => (
|
|
1197
1282
|
<AgentRow
|
|
1198
1283
|
key={agent.id}
|
|
@@ -1203,21 +1288,21 @@ export function SimpleAgentsPanel() {
|
|
|
1203
1288
|
))}
|
|
1204
1289
|
</div>
|
|
1205
1290
|
) : (
|
|
1206
|
-
<div className="rounded-
|
|
1291
|
+
<div className="rounded-2xl border border-dashed bg-card px-4 py-12 text-center">
|
|
1207
1292
|
<div className="text-sm font-medium text-foreground">
|
|
1208
1293
|
No agents yet
|
|
1209
1294
|
</div>
|
|
1210
1295
|
<div className="mt-4 flex flex-wrap justify-center gap-2">
|
|
1211
1296
|
<AgentEditorDialog
|
|
1212
|
-
onSaved={
|
|
1297
|
+
onSaved={refreshAgents}
|
|
1213
1298
|
trigger={
|
|
1214
|
-
<Button>
|
|
1299
|
+
<Button size="sm">
|
|
1215
1300
|
<IconPlus size={16} />
|
|
1216
1301
|
Create an agent
|
|
1217
1302
|
</Button>
|
|
1218
1303
|
}
|
|
1219
1304
|
/>
|
|
1220
|
-
|
|
1305
|
+
{importAction}
|
|
1221
1306
|
</div>
|
|
1222
1307
|
</div>
|
|
1223
1308
|
)}
|
|
@@ -15,6 +15,7 @@ const clientState = vi.hoisted(() => {
|
|
|
15
15
|
return {
|
|
16
16
|
actionNames,
|
|
17
17
|
legacyMutateAsync,
|
|
18
|
+
theme: "dark" as "dark" | "light",
|
|
18
19
|
workspaceSsoEnabled: false,
|
|
19
20
|
workspaceSsoMutateAsync,
|
|
20
21
|
};
|
|
@@ -90,6 +91,10 @@ vi.mock("@agent-native/core/client/i18n", () => ({
|
|
|
90
91
|
useT: () => (key: string) => key,
|
|
91
92
|
}));
|
|
92
93
|
|
|
94
|
+
vi.mock("next-themes", () => ({
|
|
95
|
+
useTheme: () => ({ resolvedTheme: clientState.theme }),
|
|
96
|
+
}));
|
|
97
|
+
|
|
93
98
|
import { WorkspaceAppFrame, WorkspaceAppKeepAlive } from "./workspace-app-host";
|
|
94
99
|
|
|
95
100
|
describe("WorkspaceAppKeepAlive", () => {
|
|
@@ -104,6 +109,7 @@ describe("WorkspaceAppKeepAlive", () => {
|
|
|
104
109
|
clientState.actionNames.length = 0;
|
|
105
110
|
clientState.legacyMutateAsync.mockClear();
|
|
106
111
|
clientState.workspaceSsoMutateAsync.mockClear();
|
|
112
|
+
clientState.theme = "dark";
|
|
107
113
|
clientState.workspaceSsoEnabled = false;
|
|
108
114
|
});
|
|
109
115
|
|
|
@@ -163,6 +169,57 @@ describe("WorkspaceAppKeepAlive", () => {
|
|
|
163
169
|
expect(clientState.legacyMutateAsync).not.toHaveBeenCalled();
|
|
164
170
|
});
|
|
165
171
|
|
|
172
|
+
it("sends the parent theme on iframe load and when the parent changes", async () => {
|
|
173
|
+
await act(async () => {
|
|
174
|
+
root.render(
|
|
175
|
+
<WorkspaceAppFrame app={{ id: "mail", name: "Mail", path: "/mail" }} />,
|
|
176
|
+
);
|
|
177
|
+
await Promise.resolve();
|
|
178
|
+
await Promise.resolve();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const iframe = container.querySelector<HTMLIFrameElement>("iframe");
|
|
182
|
+
expect(iframe).not.toBeNull();
|
|
183
|
+
if (!iframe) throw new Error("Workspace app iframe was not rendered");
|
|
184
|
+
|
|
185
|
+
const postMessage = vi.fn();
|
|
186
|
+
Object.defineProperty(iframe, "contentWindow", {
|
|
187
|
+
configurable: true,
|
|
188
|
+
value: { postMessage },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await act(async () => {
|
|
192
|
+
iframe?.dispatchEvent(new Event("load"));
|
|
193
|
+
await Promise.resolve();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(postMessage).toHaveBeenCalledWith(
|
|
197
|
+
{
|
|
198
|
+
type: "agent-native-theme-update",
|
|
199
|
+
theme: "dark",
|
|
200
|
+
isDark: true,
|
|
201
|
+
},
|
|
202
|
+
"*",
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
clientState.theme = "light";
|
|
206
|
+
await act(async () => {
|
|
207
|
+
root.render(
|
|
208
|
+
<WorkspaceAppFrame app={{ id: "mail", name: "Mail", path: "/mail" }} />,
|
|
209
|
+
);
|
|
210
|
+
await Promise.resolve();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(postMessage).toHaveBeenLastCalledWith(
|
|
214
|
+
{
|
|
215
|
+
type: "agent-native-theme-update",
|
|
216
|
+
theme: "light",
|
|
217
|
+
isDark: false,
|
|
218
|
+
},
|
|
219
|
+
"*",
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
166
223
|
it("evicts the oldest inactive app after reaching the keep-alive limit", async () => {
|
|
167
224
|
await act(async () => {
|
|
168
225
|
root.render(<WorkspaceAppKeepAlive activeAppId="mail" />);
|