@polpo-ai/dashboard 0.1.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/LICENSE +13 -0
- package/README.md +44 -0
- package/app/(dashboard)/error.tsx +47 -0
- package/app/(dashboard)/layout.tsx +41 -0
- package/app/(dashboard)/loading.tsx +30 -0
- package/app/(dashboard)/projects/[id]/agents/[name]/memory/view.tsx +36 -0
- package/app/(dashboard)/projects/[id]/agents/[name]/models-view.tsx +30 -0
- package/app/(dashboard)/projects/[id]/agents/[name]/page.tsx +51 -0
- package/app/(dashboard)/projects/[id]/agents/[name]/prompt/view.tsx +211 -0
- package/app/(dashboard)/projects/[id]/agents/[name]/skills/view.tsx +84 -0
- package/app/(dashboard)/projects/[id]/agents/[name]/tools/view.tsx +525 -0
- package/app/(dashboard)/projects/[id]/agents/[name]/vault/view.tsx +61 -0
- package/app/(dashboard)/projects/[id]/agents/page.tsx +39 -0
- package/app/(dashboard)/projects/[id]/agents/view.tsx +283 -0
- package/app/(dashboard)/projects/[id]/layout.tsx +11 -0
- package/app/(dashboard)/projects/[id]/logs/page.tsx +27 -0
- package/app/(dashboard)/projects/[id]/logs/view.tsx +184 -0
- package/app/(dashboard)/projects/[id]/memory/page.tsx +40 -0
- package/app/(dashboard)/projects/[id]/memory/view.tsx +17 -0
- package/app/(dashboard)/projects/[id]/missions/[missionId]/page.tsx +88 -0
- package/app/(dashboard)/projects/[id]/missions/[missionId]/view.tsx +569 -0
- package/app/(dashboard)/projects/[id]/missions/page.tsx +32 -0
- package/app/(dashboard)/projects/[id]/missions/view.tsx +282 -0
- package/app/(dashboard)/projects/[id]/onboarding-checklist.tsx +359 -0
- package/app/(dashboard)/projects/[id]/page.tsx +68 -0
- package/app/(dashboard)/projects/[id]/playbooks/[name]/page.tsx +5 -0
- package/app/(dashboard)/projects/[id]/playbooks/[name]/view.tsx +433 -0
- package/app/(dashboard)/projects/[id]/playbooks/page.tsx +5 -0
- package/app/(dashboard)/projects/[id]/playbooks/view.tsx +222 -0
- package/app/(dashboard)/projects/[id]/schedules/page.tsx +44 -0
- package/app/(dashboard)/projects/[id]/schedules/view.tsx +385 -0
- package/app/(dashboard)/projects/[id]/sessions/[sessionId]/page.tsx +55 -0
- package/app/(dashboard)/projects/[id]/sessions/[sessionId]/raw-view.tsx +373 -0
- package/app/(dashboard)/projects/[id]/sessions/[sessionId]/view.tsx +124 -0
- package/app/(dashboard)/projects/[id]/sessions/page.tsx +31 -0
- package/app/(dashboard)/projects/[id]/sessions/view.tsx +242 -0
- package/app/(dashboard)/projects/[id]/settings/page.tsx +52 -0
- package/app/(dashboard)/projects/[id]/skills/[name]/page.tsx +65 -0
- package/app/(dashboard)/projects/[id]/skills/[name]/view.tsx +227 -0
- package/app/(dashboard)/projects/[id]/skills/page.tsx +31 -0
- package/app/(dashboard)/projects/[id]/skills/view.tsx +243 -0
- package/app/(dashboard)/projects/[id]/storage/page.tsx +5 -0
- package/app/(dashboard)/projects/[id]/storage/view.tsx +385 -0
- package/app/(dashboard)/projects/[id]/tasks/[taskId]/activity/view.tsx +336 -0
- package/app/(dashboard)/projects/[id]/tasks/[taskId]/assessment/view.tsx +113 -0
- package/app/(dashboard)/projects/[id]/tasks/[taskId]/blueprint.ts +72 -0
- package/app/(dashboard)/projects/[id]/tasks/[taskId]/output/view.tsx +90 -0
- package/app/(dashboard)/projects/[id]/tasks/[taskId]/page.tsx +63 -0
- package/app/(dashboard)/projects/[id]/tasks/[taskId]/view.tsx +137 -0
- package/app/(dashboard)/projects/[id]/tasks/page.tsx +40 -0
- package/app/(dashboard)/projects/[id]/tasks/view.tsx +421 -0
- package/app/(dashboard)/projects/[id]/view.tsx +421 -0
- package/app/(dashboard)/projects/[id]/welcome-banner.tsx +156 -0
- package/app/(dashboard)/projects/projects-list.tsx +126 -0
- package/app/(playground)/error.tsx +42 -0
- package/app/(playground)/layout.tsx +27 -0
- package/app/(playground)/projects/[id]/playground/page.tsx +57 -0
- package/app/(playground)/projects/[id]/playground/view.tsx +167 -0
- package/app/(playground)/projects/[id]/playground-legacy/page.tsx +47 -0
- package/app/(playground)/projects/[id]/playground-legacy/skeleton.tsx +63 -0
- package/app/(playground)/projects/[id]/playground-legacy/view.tsx +237 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +385 -0
- package/app/icon.svg +6 -0
- package/app/layout.tsx +39 -0
- package/app/page.tsx +11 -0
- package/components/ai-elements/code-block.tsx +562 -0
- package/components/dashboard/agent-capabilities.tsx +326 -0
- package/components/dashboard/agent-identity-header.tsx +50 -0
- package/components/dashboard/agent-model-picker.tsx +195 -0
- package/components/dashboard/agent-model-selector.tsx +121 -0
- package/components/dashboard/agent-picker-card.tsx +46 -0
- package/components/dashboard/agent-studio.tsx +242 -0
- package/components/dashboard/agents-table.tsx +48 -0
- package/components/dashboard/breadcrumb.tsx +70 -0
- package/components/dashboard/builder-chat.tsx +418 -0
- package/components/dashboard/chat-shell.tsx +40 -0
- package/components/dashboard/client-picker.tsx +63 -0
- package/components/dashboard/command-snippet.tsx +77 -0
- package/components/dashboard/connect-dialog.tsx +680 -0
- package/components/dashboard/copy-button.tsx +45 -0
- package/components/dashboard/copy-card.tsx +48 -0
- package/components/dashboard/file-browser.tsx +161 -0
- package/components/dashboard/hint.tsx +22 -0
- package/components/dashboard/inference-mode.tsx +15 -0
- package/components/dashboard/manual-refresh-button.tsx +52 -0
- package/components/dashboard/markdown.tsx +31 -0
- package/components/dashboard/mcp-install-panel.tsx +196 -0
- package/components/dashboard/mission-graph.tsx +262 -0
- package/components/dashboard/nav-tabs.tsx +86 -0
- package/components/dashboard/polpo-chat.tsx +468 -0
- package/components/dashboard/project-copilot.tsx +253 -0
- package/components/dashboard/sdk-snippet-panel.tsx +194 -0
- package/components/dashboard/section-header.tsx +37 -0
- package/components/dashboard/settings-form.tsx +2477 -0
- package/components/dashboard/sidebar.tsx +385 -0
- package/components/dashboard/skeletons.tsx +1054 -0
- package/components/dashboard/skills-install-wizard.tsx +273 -0
- package/components/dashboard/swarm-runs.tsx +316 -0
- package/components/dashboard/tab-toggle.tsx +35 -0
- package/components/dashboard/task-studio.tsx +224 -0
- package/components/dashboard/top-header.tsx +203 -0
- package/components/dashboard/webhook-deliveries.tsx +234 -0
- package/components/icons/coding-agents.tsx +151 -0
- package/components/json-ld.tsx +8 -0
- package/components/providers.tsx +25 -0
- package/components/ui/badge.tsx +52 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/card.tsx +103 -0
- package/components/ui/chart.tsx +356 -0
- package/components/ui/command.tsx +196 -0
- package/components/ui/dialog.tsx +157 -0
- package/components/ui/input-group.tsx +158 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/multi-select.tsx +161 -0
- package/components/ui/popover.tsx +90 -0
- package/components/ui/select.tsx +201 -0
- package/components/ui/separator.tsx +25 -0
- package/components/ui/sheet.tsx +135 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/tabs.tsx +56 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +66 -0
- package/hooks/use-desktop-sidebar.tsx +50 -0
- package/hooks/use-mobile-sidebar.tsx +37 -0
- package/index.ts +14 -0
- package/lib/api.ts +194 -0
- package/lib/builder-context.ts +60 -0
- package/lib/data-client.ts +68 -0
- package/lib/get-query-client.ts +25 -0
- package/lib/polpo-client.tsx +49 -0
- package/lib/tool-catalog.ts +234 -0
- package/lib/use-event-catalog.ts +28 -0
- package/lib/utils.ts +6 -0
- package/package.json +99 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
|
|
5
|
+
|
|
6
|
+
import { cn } from "#/lib/utils"
|
|
7
|
+
import { Button } from "#/components/ui/button"
|
|
8
|
+
import { XIcon } from "lucide-react"
|
|
9
|
+
|
|
10
|
+
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
|
|
11
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
|
|
15
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
|
|
19
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
|
|
23
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
27
|
+
return (
|
|
28
|
+
<SheetPrimitive.Backdrop
|
|
29
|
+
data-slot="sheet-overlay"
|
|
30
|
+
className={cn(
|
|
31
|
+
"fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function SheetContent({
|
|
40
|
+
className,
|
|
41
|
+
children,
|
|
42
|
+
side = "right",
|
|
43
|
+
showCloseButton = true,
|
|
44
|
+
...props
|
|
45
|
+
}: SheetPrimitive.Popup.Props & {
|
|
46
|
+
side?: "top" | "right" | "bottom" | "left"
|
|
47
|
+
showCloseButton?: boolean
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<SheetPortal>
|
|
51
|
+
<SheetOverlay />
|
|
52
|
+
<SheetPrimitive.Popup
|
|
53
|
+
data-slot="sheet-content"
|
|
54
|
+
data-side={side}
|
|
55
|
+
className={cn(
|
|
56
|
+
"fixed z-50 flex flex-col gap-4 bg-background bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
{showCloseButton && (
|
|
63
|
+
<SheetPrimitive.Close
|
|
64
|
+
data-slot="sheet-close"
|
|
65
|
+
render={
|
|
66
|
+
<Button
|
|
67
|
+
variant="ghost"
|
|
68
|
+
className="absolute top-3 right-3"
|
|
69
|
+
size="icon-sm"
|
|
70
|
+
/>
|
|
71
|
+
}
|
|
72
|
+
>
|
|
73
|
+
<XIcon
|
|
74
|
+
/>
|
|
75
|
+
<span className="sr-only">Close</span>
|
|
76
|
+
</SheetPrimitive.Close>
|
|
77
|
+
)}
|
|
78
|
+
</SheetPrimitive.Popup>
|
|
79
|
+
</SheetPortal>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
data-slot="sheet-header"
|
|
87
|
+
className={cn("flex flex-col gap-0.5 p-4", className)}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
data-slot="sheet-footer"
|
|
97
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
|
104
|
+
return (
|
|
105
|
+
<SheetPrimitive.Title
|
|
106
|
+
data-slot="sheet-title"
|
|
107
|
+
className={cn("text-base font-medium text-foreground", className)}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function SheetDescription({
|
|
114
|
+
className,
|
|
115
|
+
...props
|
|
116
|
+
}: SheetPrimitive.Description.Props) {
|
|
117
|
+
return (
|
|
118
|
+
<SheetPrimitive.Description
|
|
119
|
+
data-slot="sheet-description"
|
|
120
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export {
|
|
127
|
+
Sheet,
|
|
128
|
+
SheetTrigger,
|
|
129
|
+
SheetClose,
|
|
130
|
+
SheetContent,
|
|
131
|
+
SheetHeader,
|
|
132
|
+
SheetFooter,
|
|
133
|
+
SheetTitle,
|
|
134
|
+
SheetDescription,
|
|
135
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "#/lib/utils"
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("animate-pulse rounded-md bg-muted", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
4
|
+
|
|
5
|
+
import { cn } from "#/lib/utils";
|
|
6
|
+
|
|
7
|
+
const Tabs = TabsPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
|
|
10
|
+
return (
|
|
11
|
+
<TabsPrimitive.List
|
|
12
|
+
data-slot="tabs-list"
|
|
13
|
+
className={cn(
|
|
14
|
+
"relative flex w-full items-center gap-1 overflow-x-auto border-b border-border scrollbar-none",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
|
|
23
|
+
return (
|
|
24
|
+
<TabsPrimitive.Tab
|
|
25
|
+
data-slot="tabs-trigger"
|
|
26
|
+
className={cn(
|
|
27
|
+
// base
|
|
28
|
+
"relative inline-flex items-center gap-2 whitespace-nowrap px-3 py-2 text-sm text-muted-foreground transition-colors outline-none cursor-pointer",
|
|
29
|
+
// hover
|
|
30
|
+
"hover:text-foreground",
|
|
31
|
+
// active
|
|
32
|
+
"data-[selected]:text-foreground data-[selected]:font-medium",
|
|
33
|
+
// focus
|
|
34
|
+
"focus-visible:text-foreground",
|
|
35
|
+
// disabled
|
|
36
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
37
|
+
// active underline (matches NavTabs — foreground, no layout shift)
|
|
38
|
+
"after:absolute after:-bottom-px after:left-0 after:right-0 after:h-[2px] after:bg-foreground after:opacity-0 data-[selected]:after:opacity-100 after:transition-opacity",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function TabsPanel({ className, ...props }: TabsPrimitive.Panel.Props) {
|
|
47
|
+
return (
|
|
48
|
+
<TabsPrimitive.Panel
|
|
49
|
+
data-slot="tabs-panel"
|
|
50
|
+
className={cn("mt-4 outline-none", className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Tabs, TabsList, TabsTrigger, TabsPanel };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "#/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Textarea }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
|
|
4
|
+
|
|
5
|
+
import { cn } from "#/lib/utils"
|
|
6
|
+
|
|
7
|
+
function TooltipProvider({
|
|
8
|
+
delay = 0,
|
|
9
|
+
...props
|
|
10
|
+
}: TooltipPrimitive.Provider.Props) {
|
|
11
|
+
return (
|
|
12
|
+
<TooltipPrimitive.Provider
|
|
13
|
+
data-slot="tooltip-provider"
|
|
14
|
+
delay={delay}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
|
|
21
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
|
|
25
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function TooltipContent({
|
|
29
|
+
className,
|
|
30
|
+
side = "top",
|
|
31
|
+
sideOffset = 4,
|
|
32
|
+
align = "center",
|
|
33
|
+
alignOffset = 0,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: TooltipPrimitive.Popup.Props &
|
|
37
|
+
Pick<
|
|
38
|
+
TooltipPrimitive.Positioner.Props,
|
|
39
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
40
|
+
>) {
|
|
41
|
+
return (
|
|
42
|
+
<TooltipPrimitive.Portal>
|
|
43
|
+
<TooltipPrimitive.Positioner
|
|
44
|
+
align={align}
|
|
45
|
+
alignOffset={alignOffset}
|
|
46
|
+
side={side}
|
|
47
|
+
sideOffset={sideOffset}
|
|
48
|
+
className="isolate z-50"
|
|
49
|
+
>
|
|
50
|
+
<TooltipPrimitive.Popup
|
|
51
|
+
data-slot="tooltip-content"
|
|
52
|
+
className={cn(
|
|
53
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
|
|
60
|
+
</TooltipPrimitive.Popup>
|
|
61
|
+
</TooltipPrimitive.Positioner>
|
|
62
|
+
</TooltipPrimitive.Portal>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, use, useState, useCallback, useEffect } from "react";
|
|
4
|
+
|
|
5
|
+
interface DesktopSidebarCtx {
|
|
6
|
+
open: boolean;
|
|
7
|
+
setOpen: (v: boolean) => void;
|
|
8
|
+
toggle: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DesktopSidebarContext = createContext<DesktopSidebarCtx>({
|
|
12
|
+
open: true,
|
|
13
|
+
setOpen: () => {},
|
|
14
|
+
toggle: () => {},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const STORAGE_KEY = "polpo:sidebar-open";
|
|
18
|
+
|
|
19
|
+
export function DesktopSidebarProvider({ children }: { children: React.ReactNode }) {
|
|
20
|
+
const [open, setOpenState] = useState(true);
|
|
21
|
+
|
|
22
|
+
// Hydrate from localStorage after mount (avoid SSR mismatch)
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
25
|
+
if (stored !== null) setOpenState(stored === "1");
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const setOpen = useCallback((v: boolean) => {
|
|
29
|
+
setOpenState(v);
|
|
30
|
+
localStorage.setItem(STORAGE_KEY, v ? "1" : "0");
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const toggle = useCallback(() => {
|
|
34
|
+
setOpenState((v) => {
|
|
35
|
+
const next = !v;
|
|
36
|
+
localStorage.setItem(STORAGE_KEY, next ? "1" : "0");
|
|
37
|
+
return next;
|
|
38
|
+
});
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<DesktopSidebarContext.Provider value={{ open, setOpen, toggle }}>
|
|
43
|
+
{children}
|
|
44
|
+
</DesktopSidebarContext.Provider>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useDesktopSidebar() {
|
|
49
|
+
return use(DesktopSidebarContext);
|
|
50
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, use, useState, useCallback, useEffect } from "react";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
5
|
+
|
|
6
|
+
interface MobileSidebarCtx {
|
|
7
|
+
open: boolean;
|
|
8
|
+
setOpen: (v: boolean) => void;
|
|
9
|
+
toggle: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MobileSidebarContext = createContext<MobileSidebarCtx>({
|
|
13
|
+
open: false,
|
|
14
|
+
setOpen: () => {},
|
|
15
|
+
toggle: () => {},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export function MobileSidebarProvider({ children }: { children: React.ReactNode }) {
|
|
19
|
+
const [open, setOpen] = useState(false);
|
|
20
|
+
const pathname = usePathname();
|
|
21
|
+
const toggle = useCallback(() => setOpen((v) => !v), []);
|
|
22
|
+
|
|
23
|
+
// Close sidebar on navigation
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setOpen(false);
|
|
26
|
+
}, [pathname]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<MobileSidebarContext.Provider value={{ open, setOpen, toggle }}>
|
|
30
|
+
{children}
|
|
31
|
+
</MobileSidebarContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useMobileSidebar() {
|
|
36
|
+
return use(MobileSidebarContext);
|
|
37
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of @polpo-ai/dashboard — the per-project views plus the
|
|
3
|
+
* data-client seam, for a host app (e.g. the cloud dashboard) to render inside
|
|
4
|
+
* its own shell with its own transport injected via <DataClientProvider>.
|
|
5
|
+
*
|
|
6
|
+
* Phase-2 pilot: the agents view. The remaining views follow the same shape
|
|
7
|
+
* (default-exported `<XView>` + a server `page.tsx` that fetches initial data).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { DataClientProvider, createPolpoClient } from "./lib/polpo-client";
|
|
11
|
+
export type { PolpoClientFactory } from "./lib/polpo-client";
|
|
12
|
+
|
|
13
|
+
export { default as AgentsView } from "./app/(dashboard)/projects/[id]/agents/view";
|
|
14
|
+
export type { Team } from "./app/(dashboard)/projects/[id]/agents/view";
|
package/lib/api.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side API client for the Control Plane.
|
|
3
|
+
* Used by Server Components to fetch data directly — no BFF, no API routes proxy.
|
|
4
|
+
*
|
|
5
|
+
* Data plane types (AgentConfig, Task, Mission, Team, etc.) come from @polpo-ai/core.
|
|
6
|
+
* Control plane types (Org, Project, ApiKey, ByokEntry) are defined here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { cache } from "react";
|
|
10
|
+
import { cookies } from "next/headers";
|
|
11
|
+
import { redirect } from "next/navigation";
|
|
12
|
+
import type { AgentConfig } from "@polpo-ai/core";
|
|
13
|
+
|
|
14
|
+
const API_URL = process.env.POLPO_API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";
|
|
15
|
+
const API_PREFIX = process.env.NEXT_PUBLIC_POLPO_API_PREFIX ?? "/api/v1";
|
|
16
|
+
const API_KEY = process.env.POLPO_API_KEY ?? process.env.NEXT_PUBLIC_POLPO_API_KEY;
|
|
17
|
+
|
|
18
|
+
/** Control-plane API error carrying the HTTP status, so callers can tell an
|
|
19
|
+
* auth failure (401) apart from a transient backend error (5xx/network). */
|
|
20
|
+
export class ApiError extends Error {
|
|
21
|
+
constructor(
|
|
22
|
+
public readonly status: number,
|
|
23
|
+
message: string,
|
|
24
|
+
) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "ApiError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Authenticated server-side fetch to the control plane.
|
|
32
|
+
* Forwards session cookies from the incoming request.
|
|
33
|
+
*/
|
|
34
|
+
export async function api<T>(path: string, opts?: RequestInit): Promise<T> {
|
|
35
|
+
const cookieStore = await cookies();
|
|
36
|
+
const cookieHeader = cookieStore.toString();
|
|
37
|
+
|
|
38
|
+
const res = await fetch(`${API_URL}${path}`, {
|
|
39
|
+
...opts,
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
cookie: cookieHeader,
|
|
43
|
+
...opts?.headers,
|
|
44
|
+
},
|
|
45
|
+
cache: "no-store",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
throw new ApiError(res.status, `API error ${res.status}: ${await res.text()}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return res.json();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Authenticated fetch to the data plane via session proxy.
|
|
57
|
+
* Routes through /v1/projects/:projectId/data/* which uses session auth.
|
|
58
|
+
* No API key needed — the server resolves the project internally.
|
|
59
|
+
*/
|
|
60
|
+
export async function dataApi<T>(_projectId: string, path: string, opts?: RequestInit): Promise<T> {
|
|
61
|
+
// Single-tenant self-host: straight at the OSS server (`/api/v1/...`) with a
|
|
62
|
+
// Bearer key. projectId is ignored. Callers pass `/v1/...`, so strip the
|
|
63
|
+
// leading `/v1` and let API_PREFIX carry the root. Cloud overrides with its
|
|
64
|
+
// session-proxy variant.
|
|
65
|
+
const res = await fetch(`${API_URL}${API_PREFIX}${path.replace(/^\/v1(?=\/|$)/, "")}`, {
|
|
66
|
+
...opts,
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
|
|
70
|
+
...opts?.headers,
|
|
71
|
+
},
|
|
72
|
+
cache: "no-store",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!res.ok) {
|
|
76
|
+
throw new Error(`Data API error ${res.status}: ${await res.text()}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return res.json();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cached fetch of the user's orgs — deduplicated per request via React.cache().
|
|
84
|
+
* Multiple server components in the same render can call this without duplicate HTTP requests.
|
|
85
|
+
*/
|
|
86
|
+
export const getOrgs = cache(async (): Promise<OrgWithRole[]> => {
|
|
87
|
+
try {
|
|
88
|
+
return await api<OrgWithRole[]>("/v1/orgs");
|
|
89
|
+
} catch (err) {
|
|
90
|
+
// A missing/expired session is NOT "no orgs" — sending the user to
|
|
91
|
+
// onboarding here was the bug that dumped people with a stale `.polpo.sh`
|
|
92
|
+
// cookie onto /onboarding. Re-authenticate instead.
|
|
93
|
+
if (err instanceof ApiError && err.status === 401) redirect("/login");
|
|
94
|
+
// Transient backend failure (5xx / network / DB down): surface it as a
|
|
95
|
+
// real error (error boundary) rather than masquerading as "no orgs".
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Non-throwing org fetch — returns `[]` on ANY failure (auth or transient).
|
|
102
|
+
* For contexts where a redirect/throw is wrong, e.g. API route handlers
|
|
103
|
+
* (billing identify) that must always return a response.
|
|
104
|
+
*/
|
|
105
|
+
export const getOrgsSafe = cache(() =>
|
|
106
|
+
api<OrgWithRole[]>("/v1/orgs").catch(() => [] as OrgWithRole[])
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Cached fetch of a single agent config — deduplicated per request via
|
|
111
|
+
* React.cache(). The agent detail layout and every tab page (capabilities,
|
|
112
|
+
* identity, tools, skills, prompt) call this with the same args, so within
|
|
113
|
+
* one server render the agent is fetched once instead of N times.
|
|
114
|
+
* Returns null on any error (renders the "Agent not found" empty state).
|
|
115
|
+
*/
|
|
116
|
+
export const getAgent = cache((projectId: string, name: string) =>
|
|
117
|
+
dataApi<{ ok: boolean; data: AgentConfig }>(
|
|
118
|
+
projectId,
|
|
119
|
+
`/v1/agents/${encodeURIComponent(name)}`,
|
|
120
|
+
)
|
|
121
|
+
.then((r) => r.data ?? null)
|
|
122
|
+
.catch(() => null),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// ── Control plane types (cloud-only, match packages/server/src/db/schema.ts) ──
|
|
126
|
+
|
|
127
|
+
export interface Org {
|
|
128
|
+
id: string;
|
|
129
|
+
name: string;
|
|
130
|
+
slug: string;
|
|
131
|
+
createdAt: string;
|
|
132
|
+
updatedAt: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface OrgWithRole extends Org {
|
|
136
|
+
role: "owner" | "admin" | "member";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ProjectGatewaySettings {
|
|
140
|
+
url?: string;
|
|
141
|
+
headers?: Record<string, string>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ProjectSettings {
|
|
145
|
+
gateway?: ProjectGatewaySettings;
|
|
146
|
+
[key: string]: unknown;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface OnboardingChecklist {
|
|
150
|
+
firstCompletion?: string;
|
|
151
|
+
firstSkill?: string;
|
|
152
|
+
firstTool?: string;
|
|
153
|
+
firstTask?: string;
|
|
154
|
+
firstDeploy?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface Project {
|
|
158
|
+
id: string;
|
|
159
|
+
orgId: string;
|
|
160
|
+
name: string;
|
|
161
|
+
slug: string;
|
|
162
|
+
settings?: ProjectSettings | null;
|
|
163
|
+
status: "active" | "suspended" | "deleted";
|
|
164
|
+
onboardingChecklist?: OnboardingChecklist;
|
|
165
|
+
neonProjectId?: string | null;
|
|
166
|
+
neonConnectionString?: string | null;
|
|
167
|
+
createdAt: string;
|
|
168
|
+
updatedAt: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface ApiKey {
|
|
172
|
+
id: string;
|
|
173
|
+
projectId: string;
|
|
174
|
+
name: string;
|
|
175
|
+
keyPrefix: string;
|
|
176
|
+
keyHash?: string;
|
|
177
|
+
environment: "live" | "test";
|
|
178
|
+
lastUsedAt: string | null;
|
|
179
|
+
expiresAt: string | null;
|
|
180
|
+
createdAt: string;
|
|
181
|
+
revokedAt: string | null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface ApiKeyCreated extends Omit<ApiKey, "keyHash"> {
|
|
185
|
+
rawKey: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface ByokEntry {
|
|
189
|
+
provider: string;
|
|
190
|
+
label: string | null;
|
|
191
|
+
maskedKey: string;
|
|
192
|
+
createdAt: string;
|
|
193
|
+
updatedAt: string;
|
|
194
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Derives the Builder/Copilot "context" from the current route, so the
|
|
5
|
+
* global side panel knows what the user is looking at and the Meta Agent
|
|
6
|
+
* can act accordingly (e.g. on an agent page → operate on that agent; on a
|
|
7
|
+
* skill page → that skill; elsewhere → the project section).
|
|
8
|
+
*
|
|
9
|
+
* Returns a STABLE object (memoised by value) so consumers can use it as a
|
|
10
|
+
* hook dependency without churning.
|
|
11
|
+
*/
|
|
12
|
+
import { useMemo } from "react";
|
|
13
|
+
import { useParams, usePathname } from "next/navigation";
|
|
14
|
+
|
|
15
|
+
export interface BuilderContext {
|
|
16
|
+
type: "agent" | "skill" | "section" | "project";
|
|
17
|
+
/** Human label shown in the panel's context strip, e.g. "Agent · sales-bot". */
|
|
18
|
+
label: string;
|
|
19
|
+
agentName?: string;
|
|
20
|
+
skill?: string;
|
|
21
|
+
section?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Where the Meta Agent's `navigate` tool wants to send the user. */
|
|
25
|
+
export interface NavigateTarget {
|
|
26
|
+
section: "agents" | "skills" | "tasks" | "missions" | "memory" | "playground" | "overview";
|
|
27
|
+
name?: string | null;
|
|
28
|
+
/** Agent Studio tab to focus (models/prompt/tools/skills/memory/vault). */
|
|
29
|
+
tab?: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useBuilderContext(): {
|
|
33
|
+
projectId: string | null;
|
|
34
|
+
context: BuilderContext;
|
|
35
|
+
} {
|
|
36
|
+
const params = useParams<{ id?: string; name?: string }>();
|
|
37
|
+
const pathname = usePathname() ?? "";
|
|
38
|
+
|
|
39
|
+
const projectId = params?.id ?? null;
|
|
40
|
+
const name = params?.name ? decodeURIComponent(params.name) : undefined;
|
|
41
|
+
|
|
42
|
+
// First path segment after /projects/:id/ — "agents", "skills", "tasks", …
|
|
43
|
+
const rest = pathname.match(/\/projects\/[^/]+\/([^?#]*)/)?.[1] ?? "";
|
|
44
|
+
const section = rest.split("/").filter(Boolean)[0];
|
|
45
|
+
|
|
46
|
+
return useMemo(() => {
|
|
47
|
+
let context: BuilderContext;
|
|
48
|
+
if (section === "agents" && name) {
|
|
49
|
+
context = { type: "agent", agentName: name, label: `Agent · ${name}` };
|
|
50
|
+
} else if (section === "skills" && name) {
|
|
51
|
+
context = { type: "skill", skill: name, label: `Skill · ${name}` };
|
|
52
|
+
} else if (section) {
|
|
53
|
+
const label = section.charAt(0).toUpperCase() + section.slice(1);
|
|
54
|
+
context = { type: "section", section, label };
|
|
55
|
+
} else {
|
|
56
|
+
context = { type: "project", label: "Project" };
|
|
57
|
+
}
|
|
58
|
+
return { projectId, context };
|
|
59
|
+
}, [projectId, section, name]);
|
|
60
|
+
}
|