@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
|
@@ -173,7 +173,7 @@ describe("Dispatch NavContent", () => {
|
|
|
173
173
|
expect(lists[1].className).toContain("gap-1");
|
|
174
174
|
expect(lists[1].querySelector('a[href="/admin"]')).not.toBeNull();
|
|
175
175
|
expect(lists[1].querySelector('a[href="/settings"]')).not.toBeNull();
|
|
176
|
-
expect(lists[0].querySelector("a")?.className).toContain("
|
|
176
|
+
expect(lists[0].querySelector("a")?.className).toContain("size-9");
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
it("keeps management routes out of the primary navigation", async () => {
|
|
@@ -218,7 +218,7 @@ describe("Dispatch NavContent", () => {
|
|
|
218
218
|
expect(shell?.querySelector('a[href="/admin/apps"]')).toBeNull();
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
-
it("shows ready workspace apps as
|
|
221
|
+
it("shows ready workspace apps as host links and highlights the active app", async () => {
|
|
222
222
|
clientState.workspaceApps = [
|
|
223
223
|
{ id: "calendar", name: "Calendar", path: "/calendar", status: "ready" },
|
|
224
224
|
{ id: "pending", name: "Pending", path: "/pending", status: "pending" },
|
|
@@ -226,7 +226,7 @@ describe("Dispatch NavContent", () => {
|
|
|
226
226
|
|
|
227
227
|
await act(async () => {
|
|
228
228
|
root.render(
|
|
229
|
-
<MemoryRouter initialEntries={["/calendar
|
|
229
|
+
<MemoryRouter initialEntries={["/apps/calendar"]}>
|
|
230
230
|
<TooltipProvider>
|
|
231
231
|
<NavContent />
|
|
232
232
|
</TooltipProvider>
|
|
@@ -239,7 +239,7 @@ describe("Dispatch NavContent", () => {
|
|
|
239
239
|
expect(rail?.textContent).toContain("Calendar");
|
|
240
240
|
expect(rail?.textContent).not.toContain("Pending");
|
|
241
241
|
|
|
242
|
-
const calendarLink = rail?.querySelector('a[href="/calendar"]');
|
|
242
|
+
const calendarLink = rail?.querySelector('a[href="/apps/calendar"]');
|
|
243
243
|
expect(calendarLink).not.toBeNull();
|
|
244
244
|
expect(calendarLink?.getAttribute("aria-current")).toBe("page");
|
|
245
245
|
});
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
type ComponentType,
|
|
42
42
|
type ReactNode,
|
|
43
43
|
} from "react";
|
|
44
|
-
import {
|
|
44
|
+
import { Link, useLocation, useNavigate } from "react-router";
|
|
45
45
|
|
|
46
46
|
import { cn } from "../../lib/utils";
|
|
47
47
|
import {
|
|
@@ -147,6 +147,7 @@ function pageOwnsToolbar(pathname: string): boolean {
|
|
|
147
147
|
if (pathname === "/tools" || pathname.startsWith("/tools/")) return true;
|
|
148
148
|
if (pathname === "/extensions" || pathname.startsWith("/extensions/"))
|
|
149
149
|
return true;
|
|
150
|
+
if (pathname.startsWith("/apps/")) return true;
|
|
150
151
|
return false;
|
|
151
152
|
}
|
|
152
153
|
|
|
@@ -556,7 +557,7 @@ export function NavContent({
|
|
|
556
557
|
{collapsed ? (
|
|
557
558
|
<Tooltip>
|
|
558
559
|
<TooltipTrigger asChild>
|
|
559
|
-
<
|
|
560
|
+
<Link
|
|
560
561
|
to={dispatchNavLinkTarget(item.to)}
|
|
561
562
|
onClick={(event) => {
|
|
562
563
|
if (
|
|
@@ -578,27 +579,24 @@ export function NavContent({
|
|
|
578
579
|
onNavigate?.();
|
|
579
580
|
}}
|
|
580
581
|
aria-label={label}
|
|
581
|
-
className={(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
"
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
588
|
-
);
|
|
589
|
-
}}
|
|
582
|
+
className={cn(
|
|
583
|
+
"flex size-9 items-center justify-center rounded-md text-sm",
|
|
584
|
+
itemMatchesLocalPath
|
|
585
|
+
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
586
|
+
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
587
|
+
)}
|
|
590
588
|
>
|
|
591
589
|
{Icon ? (
|
|
592
590
|
<Icon size={16} className="shrink-0" />
|
|
593
591
|
) : (
|
|
594
592
|
<span className="h-4 w-4 shrink-0" aria-hidden="true" />
|
|
595
593
|
)}
|
|
596
|
-
</
|
|
594
|
+
</Link>
|
|
597
595
|
</TooltipTrigger>
|
|
598
596
|
<TooltipContent side="right">{label}</TooltipContent>
|
|
599
597
|
</Tooltip>
|
|
600
598
|
) : (
|
|
601
|
-
<
|
|
599
|
+
<Link
|
|
602
600
|
to={dispatchNavLinkTarget(item.to)}
|
|
603
601
|
onClick={(event) => {
|
|
604
602
|
if (
|
|
@@ -619,15 +617,12 @@ export function NavContent({
|
|
|
619
617
|
}
|
|
620
618
|
onNavigate?.();
|
|
621
619
|
}}
|
|
622
|
-
className={(
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
"
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
629
|
-
);
|
|
630
|
-
}}
|
|
620
|
+
className={cn(
|
|
621
|
+
"flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm",
|
|
622
|
+
itemMatchesLocalPath
|
|
623
|
+
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
624
|
+
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
625
|
+
)}
|
|
631
626
|
>
|
|
632
627
|
{Icon ? (
|
|
633
628
|
<Icon size={16} className="shrink-0" />
|
|
@@ -635,7 +630,7 @@ export function NavContent({
|
|
|
635
630
|
<span className="h-4 w-4 shrink-0" aria-hidden="true" />
|
|
636
631
|
)}
|
|
637
632
|
<span className="truncate">{label}</span>
|
|
638
|
-
</
|
|
633
|
+
</Link>
|
|
639
634
|
)}
|
|
640
635
|
{!collapsed && item.id === "chat" && itemMatchesLocalPath ? (
|
|
641
636
|
<DispatchChatsSection onNavigate={onNavigate} />
|
|
@@ -646,46 +641,55 @@ export function NavContent({
|
|
|
646
641
|
|
|
647
642
|
return (
|
|
648
643
|
<>
|
|
649
|
-
<div
|
|
644
|
+
<div
|
|
645
|
+
className={cn(
|
|
646
|
+
"flex h-12 shrink-0 items-center border-b border-sidebar-border",
|
|
647
|
+
collapsed ? "justify-center px-0" : "px-4",
|
|
648
|
+
)}
|
|
649
|
+
>
|
|
650
650
|
<div
|
|
651
651
|
className={cn(
|
|
652
652
|
"flex items-center",
|
|
653
653
|
collapsed ? "justify-center" : "gap-2",
|
|
654
654
|
)}
|
|
655
655
|
>
|
|
656
|
+
<img
|
|
657
|
+
src={appPath("/agent-native-icon-light.svg")}
|
|
658
|
+
alt=""
|
|
659
|
+
aria-hidden="true"
|
|
660
|
+
width={35}
|
|
661
|
+
height={20}
|
|
662
|
+
className={cn(
|
|
663
|
+
"block shrink-0 object-contain object-center dark:hidden",
|
|
664
|
+
collapsed ? "h-4 w-7" : "h-5 w-[35px]",
|
|
665
|
+
)}
|
|
666
|
+
/>
|
|
667
|
+
<img
|
|
668
|
+
src={appPath("/agent-native-icon-dark.svg")}
|
|
669
|
+
alt=""
|
|
670
|
+
aria-hidden="true"
|
|
671
|
+
width={35}
|
|
672
|
+
height={20}
|
|
673
|
+
className={cn(
|
|
674
|
+
"hidden shrink-0 object-contain object-center dark:block",
|
|
675
|
+
collapsed ? "h-4 w-7" : "h-5 w-[35px]",
|
|
676
|
+
)}
|
|
677
|
+
/>
|
|
656
678
|
{!collapsed && (
|
|
657
|
-
|
|
658
|
-
<
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
height={20}
|
|
664
|
-
className="block h-5 w-[35px] shrink-0 object-contain object-center dark:hidden"
|
|
665
|
-
/>
|
|
666
|
-
<img
|
|
667
|
-
src={appPath("/agent-native-icon-dark.svg")}
|
|
668
|
-
alt=""
|
|
669
|
-
aria-hidden="true"
|
|
670
|
-
width={35}
|
|
671
|
-
height={20}
|
|
672
|
-
className="hidden h-5 w-[35px] shrink-0 object-contain object-center dark:block"
|
|
673
|
-
/>
|
|
674
|
-
<div className="min-w-0 flex-1">
|
|
675
|
-
<div
|
|
676
|
-
data-dispatch-sidebar-label
|
|
677
|
-
className="truncate text-lg font-bold tracking-tight text-foreground"
|
|
678
|
-
>
|
|
679
|
-
{DISPATCH_SIDEBAR_LABEL}
|
|
680
|
-
</div>
|
|
679
|
+
<div className="min-w-0 flex-1">
|
|
680
|
+
<div
|
|
681
|
+
data-dispatch-sidebar-label
|
|
682
|
+
className="truncate text-lg font-bold tracking-tight text-foreground"
|
|
683
|
+
>
|
|
684
|
+
{DISPATCH_SIDEBAR_LABEL}
|
|
681
685
|
</div>
|
|
682
|
-
|
|
686
|
+
</div>
|
|
683
687
|
)}
|
|
684
688
|
</div>
|
|
685
689
|
</div>
|
|
686
690
|
|
|
687
691
|
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto">
|
|
688
|
-
<nav className={cn("py-
|
|
692
|
+
<nav className={cn("py-2", collapsed ? "px-1.5" : "px-2")}>
|
|
689
693
|
<ul
|
|
690
694
|
className={cn(
|
|
691
695
|
collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5",
|
|
@@ -698,7 +702,7 @@ export function NavContent({
|
|
|
698
702
|
</nav>
|
|
699
703
|
|
|
700
704
|
<div className="mt-auto shrink-0">
|
|
701
|
-
<nav className={cn("py-1", collapsed ? "px-1" : "px-2")}>
|
|
705
|
+
<nav className={cn("py-1", collapsed ? "px-1.5" : "px-2")}>
|
|
702
706
|
<ul
|
|
703
707
|
className={cn(
|
|
704
708
|
collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5",
|
|
@@ -751,6 +755,9 @@ export function Layout({
|
|
|
751
755
|
const localPathname = localDispatchPath(location.pathname);
|
|
752
756
|
const isChatRoute =
|
|
753
757
|
localPathname === "/chat" || localPathname.startsWith("/chat/");
|
|
758
|
+
const isWorkspaceAppHostRoute = localPathname.startsWith("/apps/");
|
|
759
|
+
const sidebarBeforeAppRef = useRef<boolean | null>(null);
|
|
760
|
+
const sidebarAutoCollapsedRef = useRef(false);
|
|
754
761
|
const chatHomeHandoffActive = useAgentChatHomeHandoff({
|
|
755
762
|
storageKey: "dispatch",
|
|
756
763
|
activePath: localPathname,
|
|
@@ -765,7 +772,29 @@ export function Layout({
|
|
|
765
772
|
useAgentChatHomeHandoffLinks(chatHandoffLinkOptions);
|
|
766
773
|
|
|
767
774
|
useEffect(() => {
|
|
768
|
-
if (
|
|
775
|
+
if (isWorkspaceAppHostRoute) {
|
|
776
|
+
if (!sidebarAutoCollapsedRef.current) {
|
|
777
|
+
sidebarAutoCollapsedRef.current = true;
|
|
778
|
+
sidebarBeforeAppRef.current = sidebarCollapsed;
|
|
779
|
+
}
|
|
780
|
+
if (!sidebarCollapsed) setSidebarCollapsed(true);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (!sidebarAutoCollapsedRef.current) return;
|
|
785
|
+
sidebarAutoCollapsedRef.current = false;
|
|
786
|
+
const previousSidebarState = sidebarBeforeAppRef.current;
|
|
787
|
+
sidebarBeforeAppRef.current = null;
|
|
788
|
+
if (
|
|
789
|
+
previousSidebarState !== null &&
|
|
790
|
+
previousSidebarState !== sidebarCollapsed
|
|
791
|
+
) {
|
|
792
|
+
setSidebarCollapsed(previousSidebarState);
|
|
793
|
+
}
|
|
794
|
+
}, [isWorkspaceAppHostRoute, sidebarCollapsed]);
|
|
795
|
+
|
|
796
|
+
useEffect(() => {
|
|
797
|
+
if (typeof window === "undefined" || isWorkspaceAppHostRoute) return;
|
|
769
798
|
try {
|
|
770
799
|
window.localStorage.setItem(
|
|
771
800
|
SIDEBAR_COLLAPSE_KEY,
|
|
@@ -774,7 +803,7 @@ export function Layout({
|
|
|
774
803
|
} catch {
|
|
775
804
|
// Ignore storage failures; the in-memory preference still works.
|
|
776
805
|
}
|
|
777
|
-
}, [sidebarCollapsed]);
|
|
806
|
+
}, [isWorkspaceAppHostRoute, sidebarCollapsed]);
|
|
778
807
|
|
|
779
808
|
if (CHROMELESS_PATHS.some((path) => localPathname === path)) {
|
|
780
809
|
return <>{children}</>;
|
|
@@ -800,7 +829,9 @@ export function Layout({
|
|
|
800
829
|
<main
|
|
801
830
|
className={cn(
|
|
802
831
|
"flex-1",
|
|
803
|
-
isChatRoute
|
|
832
|
+
isChatRoute || isWorkspaceAppHostRoute
|
|
833
|
+
? "min-h-0 overflow-hidden"
|
|
834
|
+
: "overflow-y-auto",
|
|
804
835
|
)}
|
|
805
836
|
>
|
|
806
837
|
{showHeader ? (
|
|
@@ -840,7 +871,7 @@ export function Layout({
|
|
|
840
871
|
<aside
|
|
841
872
|
data-collapsed={sidebarCollapsed ? "true" : "false"}
|
|
842
873
|
className={cn(
|
|
843
|
-
"agent-layout-left-drawer hidden shrink-0 flex-col border-e bg-sidebar text-sidebar-foreground transition-[width] duration-200 ease-out lg:flex",
|
|
874
|
+
"agent-layout-left-drawer hidden shrink-0 flex-col border-e !border-e-sidebar-border bg-sidebar text-sidebar-foreground transition-[width] duration-200 ease-out lg:flex",
|
|
844
875
|
sidebarCollapsed ? "w-14" : "w-56",
|
|
845
876
|
)}
|
|
846
877
|
>
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
2
|
import { useT } from "@agent-native/core/client/i18n";
|
|
3
3
|
import { IconApps } from "@tabler/icons-react";
|
|
4
|
-
import { useLocation } from "react-router";
|
|
4
|
+
import { Link, useLocation } from "react-router";
|
|
5
5
|
|
|
6
6
|
import { cn } from "../../lib/utils";
|
|
7
7
|
import {
|
|
8
|
+
workspaceAppRoute,
|
|
8
9
|
workspaceAppHref,
|
|
9
10
|
type WorkspaceAppSummary,
|
|
10
11
|
} from "../../lib/workspace-apps";
|
|
12
|
+
import { AppIcon } from "../app-icon";
|
|
11
13
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
12
14
|
|
|
13
15
|
function pathFromValue(value: string | null | undefined): string | null {
|
|
@@ -24,6 +26,11 @@ function pathFromValue(value: string | null | undefined): string | null {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
function appMatchesPath(app: WorkspaceAppSummary, pathname: string): boolean {
|
|
29
|
+
const appRoute = workspaceAppRoute(app.id);
|
|
30
|
+
if (pathname === appRoute || pathname.startsWith(`${appRoute}/`)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
const candidatePaths = [app.path, app.url]
|
|
28
35
|
.map(pathFromValue)
|
|
29
36
|
.filter((path): path is string => !!path);
|
|
@@ -33,10 +40,6 @@ function appMatchesPath(app: WorkspaceAppSummary, pathname: string): boolean {
|
|
|
33
40
|
);
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
function appInitial(name: string): string {
|
|
37
|
-
return name.trim().charAt(0).toUpperCase() || "A";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
43
|
function appLabel(app: WorkspaceAppSummary): string {
|
|
41
44
|
return app.name.trim() || app.id;
|
|
42
45
|
}
|
|
@@ -77,31 +80,27 @@ export function WorkspaceAppsRail({
|
|
|
77
80
|
const label = appLabel(app);
|
|
78
81
|
const active = appMatchesPath(app, location.pathname);
|
|
79
82
|
const link = (
|
|
80
|
-
<
|
|
81
|
-
|
|
83
|
+
<Link
|
|
84
|
+
to={workspaceAppRoute(app.id)}
|
|
82
85
|
aria-current={active ? "page" : undefined}
|
|
83
86
|
aria-label={collapsed ? label : undefined}
|
|
84
87
|
onClick={onNavigate}
|
|
85
88
|
className={cn(
|
|
86
|
-
"flex h-
|
|
87
|
-
collapsed ? "w-
|
|
89
|
+
"flex h-9 items-center rounded-md text-sm transition-colors",
|
|
90
|
+
collapsed ? "w-9 justify-center" : "w-full gap-2 px-2 text-start",
|
|
88
91
|
active
|
|
89
92
|
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
90
93
|
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
91
94
|
)}
|
|
92
95
|
>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
)}
|
|
100
|
-
>
|
|
101
|
-
{appInitial(label)}
|
|
102
|
-
</span>
|
|
96
|
+
<AppIcon
|
|
97
|
+
id={app.id}
|
|
98
|
+
name={label}
|
|
99
|
+
size="sm"
|
|
100
|
+
className={cn("size-5 rounded-md", active && "ring-1 ring-ring/30")}
|
|
101
|
+
/>
|
|
103
102
|
{!collapsed ? <span className="truncate">{label}</span> : null}
|
|
104
|
-
</
|
|
103
|
+
</Link>
|
|
105
104
|
);
|
|
106
105
|
|
|
107
106
|
if (!collapsed) return link;
|
|
@@ -118,7 +117,7 @@ export function WorkspaceAppsRail({
|
|
|
118
117
|
<div
|
|
119
118
|
className={cn(
|
|
120
119
|
"mt-4 border-t border-sidebar-border pt-3",
|
|
121
|
-
collapsed ? "px-1" : "px-2",
|
|
120
|
+
collapsed ? "px-1.5" : "px-2",
|
|
122
121
|
)}
|
|
123
122
|
data-dispatch-apps-rail
|
|
124
123
|
>
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { IconStack2 } from "@tabler/icons-react";
|
|
3
|
+
|
|
4
|
+
import { filterOtherApps, type ConnectedAppSummary } from "../lib/other-apps";
|
|
5
|
+
import type { WorkspaceAppId } from "../lib/other-apps";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
import { ActionQueryError } from "./action-query-error";
|
|
8
|
+
import {
|
|
9
|
+
APP_LIST_GRID_CLASS,
|
|
10
|
+
APP_LIST_GRID_ROW_CLASS,
|
|
11
|
+
AppList,
|
|
12
|
+
} from "./app-list-row";
|
|
13
|
+
import { ConnectedAppCard } from "./connected-app-card";
|
|
14
|
+
import { Skeleton } from "./ui/skeleton";
|
|
15
|
+
import {
|
|
16
|
+
WorkspaceTemplateCard,
|
|
17
|
+
type CuratedWorkspaceTemplate,
|
|
18
|
+
type CuratedWorkspaceTemplatesResult,
|
|
19
|
+
type WorkspaceTemplateLabels,
|
|
20
|
+
} from "./workspace-template-card";
|
|
21
|
+
|
|
22
|
+
type OtherAppEntry =
|
|
23
|
+
| { kind: "template"; template: CuratedWorkspaceTemplate }
|
|
24
|
+
| { kind: "connected"; app: ConnectedAppSummary };
|
|
25
|
+
|
|
26
|
+
function templateKey(template: CuratedWorkspaceTemplate): string {
|
|
27
|
+
return (template.templateId || template.id || template.appId || template.name)
|
|
28
|
+
.trim()
|
|
29
|
+
.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getTemplateItems(
|
|
33
|
+
result: CuratedWorkspaceTemplatesResult | undefined,
|
|
34
|
+
): CuratedWorkspaceTemplate[] {
|
|
35
|
+
if (!result) return [];
|
|
36
|
+
return Array.isArray(result) ? result : result.templates;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function mergeOtherAppEntries({
|
|
40
|
+
templates,
|
|
41
|
+
connectedApps,
|
|
42
|
+
workspaceApps,
|
|
43
|
+
}: {
|
|
44
|
+
templates?: CuratedWorkspaceTemplatesResult;
|
|
45
|
+
connectedApps: ConnectedAppSummary[];
|
|
46
|
+
workspaceApps: WorkspaceAppId[];
|
|
47
|
+
}): OtherAppEntry[] {
|
|
48
|
+
const workspaceAppIds = new Set(
|
|
49
|
+
workspaceApps.map((app) => app.id.trim().toLowerCase()),
|
|
50
|
+
);
|
|
51
|
+
const seen = new Set<string>();
|
|
52
|
+
const entries: OtherAppEntry[] = [];
|
|
53
|
+
|
|
54
|
+
for (const template of getTemplateItems(templates)) {
|
|
55
|
+
const id = templateKey(template);
|
|
56
|
+
if (!id || template.installed || workspaceAppIds.has(id)) continue;
|
|
57
|
+
seen.add(id);
|
|
58
|
+
entries.push({ kind: "template", template });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const app of filterOtherApps(connectedApps, workspaceApps)) {
|
|
62
|
+
const id = app.id.trim().toLowerCase();
|
|
63
|
+
if (seen.has(id)) continue;
|
|
64
|
+
seen.add(id);
|
|
65
|
+
entries.push({ kind: "connected", app });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return entries;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function OtherAppsSection({
|
|
72
|
+
templates,
|
|
73
|
+
connectedApps = [],
|
|
74
|
+
workspaceApps,
|
|
75
|
+
templatesLoading = false,
|
|
76
|
+
connectedAppsLoading = false,
|
|
77
|
+
templatesError,
|
|
78
|
+
connectedAppsError,
|
|
79
|
+
onRetryTemplates,
|
|
80
|
+
onRetryConnectedApps,
|
|
81
|
+
templateLabels,
|
|
82
|
+
onRemixSuccess,
|
|
83
|
+
className,
|
|
84
|
+
}: {
|
|
85
|
+
templates?: CuratedWorkspaceTemplatesResult;
|
|
86
|
+
connectedApps?: ConnectedAppSummary[];
|
|
87
|
+
workspaceApps: WorkspaceAppId[];
|
|
88
|
+
templatesLoading?: boolean;
|
|
89
|
+
connectedAppsLoading?: boolean;
|
|
90
|
+
templatesError?: Error | null;
|
|
91
|
+
connectedAppsError?: Error | null;
|
|
92
|
+
onRetryTemplates?: () => void;
|
|
93
|
+
onRetryConnectedApps?: () => void;
|
|
94
|
+
templateLabels?: Partial<WorkspaceTemplateLabels>;
|
|
95
|
+
onRemixSuccess?: (
|
|
96
|
+
result: unknown,
|
|
97
|
+
template: CuratedWorkspaceTemplate,
|
|
98
|
+
) => void;
|
|
99
|
+
className?: string;
|
|
100
|
+
}) {
|
|
101
|
+
const t = useT();
|
|
102
|
+
const entries = mergeOtherAppEntries({
|
|
103
|
+
templates,
|
|
104
|
+
connectedApps,
|
|
105
|
+
workspaceApps,
|
|
106
|
+
});
|
|
107
|
+
const isLoading = templatesLoading || connectedAppsLoading;
|
|
108
|
+
const hasError = Boolean(templatesError || connectedAppsError);
|
|
109
|
+
|
|
110
|
+
if (!isLoading && !hasError && entries.length === 0) return null;
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<section className={cn("space-y-3 border-t pt-6", className)}>
|
|
114
|
+
<div className="flex min-w-0 items-start gap-2">
|
|
115
|
+
<IconStack2
|
|
116
|
+
size={16}
|
|
117
|
+
className="mt-0.5 shrink-0 text-muted-foreground"
|
|
118
|
+
/>
|
|
119
|
+
<div className="min-w-0">
|
|
120
|
+
<h2 className="truncate text-sm font-semibold text-foreground">
|
|
121
|
+
{t("dispatch.pages.otherApps", { defaultValue: "Other apps" })}
|
|
122
|
+
</h2>
|
|
123
|
+
{entries.length > 0 ? (
|
|
124
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
125
|
+
{t("dispatch.pages.availableCount", {
|
|
126
|
+
count: entries.length,
|
|
127
|
+
})}
|
|
128
|
+
</p>
|
|
129
|
+
) : null}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{templatesError ? (
|
|
134
|
+
<ActionQueryError
|
|
135
|
+
error={templatesError}
|
|
136
|
+
onRetry={() => onRetryTemplates?.()}
|
|
137
|
+
/>
|
|
138
|
+
) : null}
|
|
139
|
+
{connectedAppsError ? (
|
|
140
|
+
<ActionQueryError
|
|
141
|
+
error={connectedAppsError}
|
|
142
|
+
onRetry={() => onRetryConnectedApps?.()}
|
|
143
|
+
/>
|
|
144
|
+
) : null}
|
|
145
|
+
|
|
146
|
+
{isLoading && entries.length === 0 ? (
|
|
147
|
+
<OtherAppsSkeletonList />
|
|
148
|
+
) : entries.length > 0 ? (
|
|
149
|
+
<AppList className={APP_LIST_GRID_CLASS}>
|
|
150
|
+
{entries.map((entry) =>
|
|
151
|
+
entry.kind === "template" ? (
|
|
152
|
+
<WorkspaceTemplateCard
|
|
153
|
+
key={`template:${templateKey(entry.template)}`}
|
|
154
|
+
template={entry.template}
|
|
155
|
+
labels={templateLabels}
|
|
156
|
+
catalog
|
|
157
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
158
|
+
onRemixSuccess={onRemixSuccess}
|
|
159
|
+
/>
|
|
160
|
+
) : (
|
|
161
|
+
<ConnectedAppCard
|
|
162
|
+
key={`connected:${entry.app.id}`}
|
|
163
|
+
app={entry.app}
|
|
164
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
165
|
+
/>
|
|
166
|
+
),
|
|
167
|
+
)}
|
|
168
|
+
</AppList>
|
|
169
|
+
) : null}
|
|
170
|
+
</section>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function OtherAppsSkeletonList() {
|
|
175
|
+
return (
|
|
176
|
+
<AppList className={APP_LIST_GRID_CLASS}>
|
|
177
|
+
{Array.from({ length: 4 }).map((_, index) => (
|
|
178
|
+
<div
|
|
179
|
+
key={index}
|
|
180
|
+
className={cn(
|
|
181
|
+
"flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0",
|
|
182
|
+
APP_LIST_GRID_ROW_CLASS,
|
|
183
|
+
)}
|
|
184
|
+
>
|
|
185
|
+
<Skeleton className="size-8 shrink-0 rounded-xl" />
|
|
186
|
+
<div className="min-w-0 flex-1 space-y-2">
|
|
187
|
+
<Skeleton className="h-4 w-32" />
|
|
188
|
+
<Skeleton className="h-3 w-2/3" />
|
|
189
|
+
</div>
|
|
190
|
+
<Skeleton className="h-9 w-24 shrink-0 rounded-md" />
|
|
191
|
+
</div>
|
|
192
|
+
))}
|
|
193
|
+
</AppList>
|
|
194
|
+
);
|
|
195
|
+
}
|