@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,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "#/components/ui/select";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Agent picker styled as a card: pill label + select dropdown.
|
|
7
|
+
* Shared across ConnectDialog SDK tab and OnboardingChecklist chat step.
|
|
8
|
+
*/
|
|
9
|
+
export function AgentPickerCard({
|
|
10
|
+
agents,
|
|
11
|
+
agent,
|
|
12
|
+
onChange,
|
|
13
|
+
show,
|
|
14
|
+
}: {
|
|
15
|
+
agents: string[] | null;
|
|
16
|
+
agent: string;
|
|
17
|
+
onChange: (v: string) => void;
|
|
18
|
+
show: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
if (!show) return null;
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex flex-col gap-2 border border-border bg-background p-3">
|
|
23
|
+
<span className="inline-flex h-5 w-fit items-center rounded bg-secondary px-2 text-[11px] font-medium text-muted-foreground">
|
|
24
|
+
agent
|
|
25
|
+
</span>
|
|
26
|
+
{agents && agents.length > 1 ? (
|
|
27
|
+
<Select value={agent} onValueChange={(v) => { if (v) onChange(v); }}>
|
|
28
|
+
<SelectTrigger className="w-full font-mono text-sm">
|
|
29
|
+
<SelectValue />
|
|
30
|
+
</SelectTrigger>
|
|
31
|
+
<SelectContent>
|
|
32
|
+
{agents.map((name) => (
|
|
33
|
+
<SelectItem key={name} value={name}>
|
|
34
|
+
{name}
|
|
35
|
+
</SelectItem>
|
|
36
|
+
))}
|
|
37
|
+
</SelectContent>
|
|
38
|
+
</Select>
|
|
39
|
+
) : (
|
|
40
|
+
<p className="font-mono text-sm leading-6 text-foreground">
|
|
41
|
+
{agents === null ? "Loading…" : agent}
|
|
42
|
+
</p>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Studio — the agent detail surface, rendered as a chat canvas:
|
|
5
|
+
* left = the full agent editor (Models / Prompt / Tools / Skills /
|
|
6
|
+
* Memory / Vault, client-switched tabs), right = the Meta Agent builder
|
|
7
|
+
* chat that opens/closes (collapses by width → stays mounted, no reset).
|
|
8
|
+
*
|
|
9
|
+
* This IS the agent detail page (`/projects/[id]/agents/[name]`). Builder
|
|
10
|
+
* closed = plain detail; "Edit" reveals the chat alongside.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useEffect, useState } from "react";
|
|
14
|
+
import Link from "next/link";
|
|
15
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
16
|
+
import {
|
|
17
|
+
Bot,
|
|
18
|
+
Cpu,
|
|
19
|
+
BookMarked,
|
|
20
|
+
Wrench,
|
|
21
|
+
FileText,
|
|
22
|
+
Brain,
|
|
23
|
+
Lock,
|
|
24
|
+
Users,
|
|
25
|
+
MessageSquare,
|
|
26
|
+
MessagesSquare,
|
|
27
|
+
ExternalLink,
|
|
28
|
+
type LucideIcon,
|
|
29
|
+
} from "lucide-react";
|
|
30
|
+
import type { AgentConfig } from "@polpo-ai/core";
|
|
31
|
+
import { BuilderChat } from "#/components/dashboard/builder-chat";
|
|
32
|
+
import { AgentIdentityHeader } from "#/components/dashboard/agent-identity-header";
|
|
33
|
+
import { Breadcrumb } from "#/components/dashboard/breadcrumb";
|
|
34
|
+
import { useCopilot } from "#/components/dashboard/project-copilot";
|
|
35
|
+
import AgentModelsView from "#/app/(dashboard)/projects/[id]/agents/[name]/models-view";
|
|
36
|
+
import AgentSkillsView from "#/app/(dashboard)/projects/[id]/agents/[name]/skills/view";
|
|
37
|
+
import AgentToolsView from "#/app/(dashboard)/projects/[id]/agents/[name]/tools/view";
|
|
38
|
+
import AgentPromptView from "#/app/(dashboard)/projects/[id]/agents/[name]/prompt/view";
|
|
39
|
+
import AgentMemoryView from "#/app/(dashboard)/projects/[id]/agents/[name]/memory/view";
|
|
40
|
+
import AgentVaultView from "#/app/(dashboard)/projects/[id]/agents/[name]/vault/view";
|
|
41
|
+
|
|
42
|
+
export interface VaultEntry {
|
|
43
|
+
service: string;
|
|
44
|
+
type: string;
|
|
45
|
+
label: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type StudioTab = "models" | "prompt" | "tools" | "skills" | "memory" | "vault";
|
|
49
|
+
|
|
50
|
+
const TABS: { key: StudioTab; label: string; icon: LucideIcon }[] = [
|
|
51
|
+
{ key: "models", label: "Models", icon: Cpu },
|
|
52
|
+
{ key: "prompt", label: "Instructions", icon: FileText },
|
|
53
|
+
{ key: "tools", label: "Tools", icon: Wrench },
|
|
54
|
+
{ key: "skills", label: "Skills", icon: BookMarked },
|
|
55
|
+
{ key: "memory", label: "Memory", icon: Brain },
|
|
56
|
+
{ key: "vault", label: "Vault", icon: Lock },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
export function AgentStudio({
|
|
60
|
+
projectId,
|
|
61
|
+
apiUrl,
|
|
62
|
+
agent,
|
|
63
|
+
memoryContent,
|
|
64
|
+
vaultEntries,
|
|
65
|
+
}: {
|
|
66
|
+
projectId: string;
|
|
67
|
+
apiUrl: string;
|
|
68
|
+
agent: AgentConfig | null;
|
|
69
|
+
memoryContent: string;
|
|
70
|
+
vaultEntries: VaultEntry[];
|
|
71
|
+
}) {
|
|
72
|
+
const router = useRouter();
|
|
73
|
+
const copilot = useCopilot();
|
|
74
|
+
// Soft-disabled: the in-Studio docked builder stays in the tree but never
|
|
75
|
+
// opens — "Edit" now opens the global lateral Copilot (Ask Polpo) instead.
|
|
76
|
+
const chatOpen = false;
|
|
77
|
+
|
|
78
|
+
if (!agent) {
|
|
79
|
+
return (
|
|
80
|
+
<div className="border border-border p-10 text-center">
|
|
81
|
+
<Bot className="mx-auto h-5 w-5 text-muted-foreground/40" strokeWidth={1.5} />
|
|
82
|
+
<p className="mt-2 text-sm text-muted-foreground">Agent not found.</p>
|
|
83
|
+
<Link
|
|
84
|
+
href={`/projects/${projectId}/agents`}
|
|
85
|
+
className="mt-3 inline-block text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground"
|
|
86
|
+
>
|
|
87
|
+
Back to agents
|
|
88
|
+
</Link>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
// Fills the viewport (no page scroll); only inner panes scroll.
|
|
95
|
+
<div className="flex h-[calc(100vh-7rem)] min-h-[520px] flex-col overflow-hidden">
|
|
96
|
+
{/* Top bar — breadcrumb + actions. Fixed (does not scroll). */}
|
|
97
|
+
<div className="flex shrink-0 items-center gap-2 pb-3">
|
|
98
|
+
<Breadcrumb
|
|
99
|
+
items={[
|
|
100
|
+
{ label: "Agents", href: `/projects/${projectId}/agents`, icon: Users },
|
|
101
|
+
{ label: agent.name },
|
|
102
|
+
]}
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
<div className="ml-auto flex items-center gap-2">
|
|
106
|
+
{/* Test = chat with the live agent in the playground (new tab). */}
|
|
107
|
+
<Link
|
|
108
|
+
href={`/projects/${projectId}/playground?agent=${encodeURIComponent(agent.name)}`}
|
|
109
|
+
target="_blank"
|
|
110
|
+
rel="noopener noreferrer"
|
|
111
|
+
className="inline-flex items-center gap-1.5 border border-border bg-card px-2.5 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:border-foreground/30 hover:text-foreground"
|
|
112
|
+
>
|
|
113
|
+
<MessageSquare className="h-3.5 w-3.5" />
|
|
114
|
+
Test
|
|
115
|
+
<ExternalLink className="h-3 w-3 text-muted-foreground/60" />
|
|
116
|
+
</Link>
|
|
117
|
+
{/* Edit = open the global lateral Copilot (Polpo AI), scoped to
|
|
118
|
+
this agent via the route context. Hidden once it's open. */}
|
|
119
|
+
{!copilot?.open && (
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
onClick={() => copilot?.setOpen(true)}
|
|
123
|
+
className="inline-flex items-center gap-1.5 border border-border bg-card px-2.5 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:border-foreground/30 hover:text-foreground"
|
|
124
|
+
>
|
|
125
|
+
<MessagesSquare className="h-3.5 w-3.5" strokeWidth={1.5} />
|
|
126
|
+
Edit
|
|
127
|
+
</button>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Split — content LEFT, builder chat RIGHT. The chat collapses by
|
|
133
|
+
WIDTH (stays mounted → no reset). A full-height vertical
|
|
134
|
+
separator divides the panes when the builder is open. */}
|
|
135
|
+
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
136
|
+
<div className="min-w-0 flex-1 overflow-hidden">
|
|
137
|
+
<FullAgentEditor
|
|
138
|
+
agent={agent}
|
|
139
|
+
projectId={projectId}
|
|
140
|
+
memoryContent={memoryContent}
|
|
141
|
+
vaultEntries={vaultEntries}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
<div
|
|
145
|
+
className={`shrink-0 overflow-hidden transition-[width] duration-200 ease-out ${
|
|
146
|
+
chatOpen ? "w-[36%] border-l border-border" : "w-0"
|
|
147
|
+
}`}
|
|
148
|
+
>
|
|
149
|
+
<div className="flex h-full w-full min-w-[320px] flex-col overflow-hidden">
|
|
150
|
+
<BuilderChat
|
|
151
|
+
projectId={projectId}
|
|
152
|
+
apiUrl={apiUrl}
|
|
153
|
+
agentName={agent.name}
|
|
154
|
+
onMutation={() => router.refresh()}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function FullAgentEditor({
|
|
164
|
+
agent,
|
|
165
|
+
projectId,
|
|
166
|
+
memoryContent,
|
|
167
|
+
vaultEntries,
|
|
168
|
+
}: {
|
|
169
|
+
agent: AgentConfig;
|
|
170
|
+
projectId: string;
|
|
171
|
+
memoryContent: string;
|
|
172
|
+
vaultEntries: VaultEntry[];
|
|
173
|
+
}) {
|
|
174
|
+
// Tab is URL-driven (?tab=) so the Meta Agent's `navigate` tool can point
|
|
175
|
+
// straight at a component, and the tab is deep-linkable.
|
|
176
|
+
const router = useRouter();
|
|
177
|
+
const pathname = usePathname();
|
|
178
|
+
const searchParams = useSearchParams();
|
|
179
|
+
const urlTab = searchParams.get("tab") as StudioTab | null;
|
|
180
|
+
const validUrlTab = urlTab && TABS.some((t) => t.key === urlTab) ? urlTab : null;
|
|
181
|
+
const [tab, setTab] = useState<StudioTab>(validUrlTab ?? "models");
|
|
182
|
+
|
|
183
|
+
// React to external URL changes (e.g. the copilot navigating here).
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (validUrlTab && validUrlTab !== tab) setTab(validUrlTab);
|
|
186
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
187
|
+
}, [validUrlTab]);
|
|
188
|
+
|
|
189
|
+
function selectTab(key: StudioTab) {
|
|
190
|
+
setTab(key);
|
|
191
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
192
|
+
params.set("tab", key);
|
|
193
|
+
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div className="flex h-full flex-col">
|
|
198
|
+
{/* Sticky: identity + tab nav (don't scroll) */}
|
|
199
|
+
<div className="shrink-0 pt-1 pr-4">
|
|
200
|
+
<AgentIdentityHeader name={agent.name} role={agent.role} />
|
|
201
|
+
<div className="mt-4 flex flex-wrap gap-1 border-b border-border">
|
|
202
|
+
{TABS.map((t) => {
|
|
203
|
+
const Icon = t.icon;
|
|
204
|
+
const active = t.key === tab;
|
|
205
|
+
return (
|
|
206
|
+
<button
|
|
207
|
+
key={t.key}
|
|
208
|
+
type="button"
|
|
209
|
+
onClick={() => selectTab(t.key)}
|
|
210
|
+
className={`relative flex items-center gap-1.5 px-3 py-2 text-sm transition-colors ${
|
|
211
|
+
active
|
|
212
|
+
? "font-medium text-foreground"
|
|
213
|
+
: "text-muted-foreground hover:text-foreground"
|
|
214
|
+
}`}
|
|
215
|
+
>
|
|
216
|
+
<Icon className="h-3.5 w-3.5" strokeWidth={1.5} />
|
|
217
|
+
{t.label}
|
|
218
|
+
{active && (
|
|
219
|
+
<span className="absolute bottom-0 left-0 right-0 h-[2px] bg-foreground" />
|
|
220
|
+
)}
|
|
221
|
+
</button>
|
|
222
|
+
);
|
|
223
|
+
})}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
{/* Only the tab content scrolls */}
|
|
228
|
+
<div className="min-h-0 flex-1 overflow-auto py-4 pr-4">
|
|
229
|
+
{tab === "models" && (
|
|
230
|
+
<AgentModelsView agent={agent} projectId={projectId} agentName={agent.name} />
|
|
231
|
+
)}
|
|
232
|
+
{tab === "skills" && <AgentSkillsView agent={agent} projectId={projectId} />}
|
|
233
|
+
{tab === "tools" && <AgentToolsView agent={agent} projectId={projectId} />}
|
|
234
|
+
{tab === "prompt" && (
|
|
235
|
+
<AgentPromptView agent={agent} projectId={projectId} agentName={agent.name} />
|
|
236
|
+
)}
|
|
237
|
+
{tab === "memory" && <AgentMemoryView id={projectId} content={memoryContent} />}
|
|
238
|
+
{tab === "vault" && <AgentVaultView entries={vaultEntries} />}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
|
+
import type { AgentConfig } from "@polpo-ai/core";
|
|
4
|
+
|
|
5
|
+
const headerClass = "py-2.5 text-left text-xs font-medium text-muted-foreground";
|
|
6
|
+
|
|
7
|
+
export function AgentsTable({ agents, projectId }: { agents: AgentConfig[]; projectId: string }) {
|
|
8
|
+
return (
|
|
9
|
+
<div data-testid="agents-table" className="mt-4 rounded-lg border border-border overflow-hidden overflow-x-auto min-w-[600px]">
|
|
10
|
+
{/* Header — same flex widths as rows */}
|
|
11
|
+
<div className="flex items-center border-b border-border bg-secondary/50">
|
|
12
|
+
<span className={`px-4 flex-1 min-w-[120px] ${headerClass}`}>Agent</span>
|
|
13
|
+
<span className={`px-4 flex-1 ${headerClass}`}>Role</span>
|
|
14
|
+
<span className={`px-4 flex-1 ${headerClass}`}>Model</span>
|
|
15
|
+
<span className={`px-4 w-20 ${headerClass}`}>Tools</span>
|
|
16
|
+
<span className={`px-4 w-24 ${headerClass}`}>Reasoning</span>
|
|
17
|
+
<span className="w-8" />
|
|
18
|
+
</div>
|
|
19
|
+
{/* Rows */}
|
|
20
|
+
{agents.map((agent) => (
|
|
21
|
+
<Link
|
|
22
|
+
key={agent.name}
|
|
23
|
+
href={`/projects/${projectId}/agents/${agent.name}`}
|
|
24
|
+
data-testid={`agent-row-${agent.name}`}
|
|
25
|
+
className="flex items-center border-b border-border last:border-0 hover:bg-secondary/30 transition-colors group"
|
|
26
|
+
>
|
|
27
|
+
<span className="px-4 py-3 flex-1 min-w-[120px]">
|
|
28
|
+
<span className="font-mono text-xs font-medium">{agent.name}</span>
|
|
29
|
+
{agent.identity?.title && (
|
|
30
|
+
<span className="block mt-0.5 text-[11px] text-muted-foreground/50">{agent.identity.title}</span>
|
|
31
|
+
)}
|
|
32
|
+
</span>
|
|
33
|
+
<span className="px-4 py-3 flex-1 text-sm text-muted-foreground">{agent.role ?? "—"}</span>
|
|
34
|
+
<span className="px-4 py-3 flex-1 font-mono text-xs text-muted-foreground">{agent.model ?? "—"}</span>
|
|
35
|
+
<span className="px-4 py-3 w-20 text-xs text-muted-foreground">
|
|
36
|
+
{agent.allowedTools ? agent.allowedTools.length : 0} tools
|
|
37
|
+
</span>
|
|
38
|
+
<span className="px-4 py-3 w-24 text-xs text-muted-foreground">
|
|
39
|
+
{agent.reasoning ?? "off"}
|
|
40
|
+
</span>
|
|
41
|
+
<span className="px-2 py-3 w-8">
|
|
42
|
+
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground/30 group-hover:text-muted-foreground transition-colors" />
|
|
43
|
+
</span>
|
|
44
|
+
</Link>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Fragment } from "react";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { ChevronRight, type LucideIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
export interface Crumb {
|
|
6
|
+
label: string;
|
|
7
|
+
href?: string;
|
|
8
|
+
icon?: LucideIcon;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Shared breadcrumb — chevron-separated trail that reads unmistakably as a
|
|
13
|
+
* breadcrumb. Intermediate crumbs are muted links with a hover chip; the
|
|
14
|
+
* last crumb is the current location (foreground, mono), still linkable if
|
|
15
|
+
* it has an href. One component, used across the agent surfaces (DRY).
|
|
16
|
+
*/
|
|
17
|
+
export function Breadcrumb({ items }: { items: Crumb[] }) {
|
|
18
|
+
return (
|
|
19
|
+
<nav
|
|
20
|
+
aria-label="Breadcrumb"
|
|
21
|
+
className="flex items-center gap-0.5 text-xs"
|
|
22
|
+
>
|
|
23
|
+
{items.map((item, i) => {
|
|
24
|
+
const isLast = i === items.length - 1;
|
|
25
|
+
const Icon = item.icon;
|
|
26
|
+
const inner = (
|
|
27
|
+
<span className="inline-flex items-center gap-1.5">
|
|
28
|
+
{Icon && <Icon className="h-3.5 w-3.5 shrink-0" strokeWidth={1.5} />}
|
|
29
|
+
<span className={isLast ? "font-mono font-medium" : ""}>
|
|
30
|
+
{item.label}
|
|
31
|
+
</span>
|
|
32
|
+
</span>
|
|
33
|
+
);
|
|
34
|
+
return (
|
|
35
|
+
<Fragment key={`${item.label}-${i}`}>
|
|
36
|
+
{i > 0 && (
|
|
37
|
+
<ChevronRight
|
|
38
|
+
className="h-3.5 w-3.5 shrink-0 text-muted-foreground/40"
|
|
39
|
+
strokeWidth={2}
|
|
40
|
+
aria-hidden
|
|
41
|
+
/>
|
|
42
|
+
)}
|
|
43
|
+
{item.href ? (
|
|
44
|
+
<Link
|
|
45
|
+
href={item.href}
|
|
46
|
+
aria-current={isLast ? "page" : undefined}
|
|
47
|
+
className={`inline-flex items-center rounded-sm px-1.5 py-1 transition-colors hover:bg-secondary ${
|
|
48
|
+
isLast
|
|
49
|
+
? "text-foreground hover:text-foreground"
|
|
50
|
+
: "text-muted-foreground hover:text-foreground"
|
|
51
|
+
}`}
|
|
52
|
+
>
|
|
53
|
+
{inner}
|
|
54
|
+
</Link>
|
|
55
|
+
) : (
|
|
56
|
+
<span
|
|
57
|
+
aria-current={isLast ? "page" : undefined}
|
|
58
|
+
className={`inline-flex items-center px-1.5 py-1 ${
|
|
59
|
+
isLast ? "text-foreground" : "text-muted-foreground"
|
|
60
|
+
}`}
|
|
61
|
+
>
|
|
62
|
+
{inner}
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
</Fragment>
|
|
66
|
+
);
|
|
67
|
+
})}
|
|
68
|
+
</nav>
|
|
69
|
+
);
|
|
70
|
+
}
|