Patchpass V1
Deploy / Build (push) Successful in 28s
Deploy / Test & Lint (push) Failing after 29s
Deploy / Build and Push Docker Image (push) Has been skipped

This commit is contained in:
Space-Banane
2026-07-18 20:14:44 +02:00
commit 7e05dd918c
101 changed files with 15183 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
import { useCallback, useEffect, useState } from "react";
import { toast } from "react-toastify";
import { agents as agentsApi } from "../api/client";
import type { Agent } from "../api/types";
import { Button, Card, EmptyState, PageHeader, Spinner } from "../components/ui";
import { AgentAvatar } from "../components/AgentAvatar";
import { AgentFormModal, ConnectModal } from "../components/AgentModals";
export function AgentsPage() {
const [agents, setAgents] = useState<Agent[]>([]);
const [loading, setLoading] = useState(true);
const [formOpen, setFormOpen] = useState(false);
const [editing, setEditing] = useState<Agent | null>(null);
const [connectAgent, setConnectAgent] = useState<Agent | null>(null);
const [revealedKey, setRevealedKey] = useState<string | null>(null);
const load = useCallback(async () => {
try {
setAgents(await agentsApi.list());
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
load();
}, [load]);
const onSaved = (agent: Agent, created: boolean) => {
load();
if (created && agent.api_key) {
setRevealedKey(agent.api_key);
setConnectAgent(agent);
}
};
const regenerate = async (agent: Agent) => {
if (!confirm(`Regenerate the API key for "${agent.name}"? The old key stops working immediately.`)) return;
try {
const updated = await agentsApi.regenerateKey(agent.id);
toast.success("API key regenerated");
setRevealedKey(updated.api_key ?? null);
setConnectAgent(updated);
load();
} catch (err) {
toast.error(err instanceof Error ? err.message : "Failed");
}
};
const toggleDisabled = async (agent: Agent) => {
try {
await agentsApi.setDisabled(agent.id, !agent.disabled);
toast.success(agent.disabled ? "Agent enabled" : "Agent disabled");
load();
} catch (err) {
toast.error(err instanceof Error ? err.message : "Failed");
}
};
const remove = async (agent: Agent) => {
if (!confirm(`Delete "${agent.name}"? All its requests will be permanently deleted.`)) return;
try {
await agentsApi.remove(agent.id);
toast.success("Agent deleted");
load();
} catch (err) {
toast.error(err instanceof Error ? err.message : "Failed");
}
};
return (
<div>
<PageHeader
title="Agents"
subtitle="Each agent has its own API key. You may own up to 5 agents."
actions={
<Button
onClick={() => {
setEditing(null);
setFormOpen(true);
}}
disabled={agents.length >= 5}
>
+ New agent
</Button>
}
/>
{loading ? (
<div className="flex justify-center py-16 text-primary">
<Spinner className="h-6 w-6" />
</div>
) : agents.length === 0 ? (
<EmptyState
icon="🤖"
title="No agents yet"
subtitle="Create an agent, then connect it via OpenClaw, MCP, or the REST API."
/>
) : (
<div className="grid gap-4 md:grid-cols-2">
{agents.map((a) => (
<Card key={a.id} className="p-4">
<div className="flex items-start gap-3">
<AgentAvatar name={a.name} iconUrl={a.icon_url} size={44} />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<h3 className="truncate font-semibold text-text">{a.name}</h3>
{a.disabled && (
<span className="rounded-full bg-rejected/15 px-2 py-0.5 text-[10px] font-semibold text-rejected">
DISABLED
</span>
)}
</div>
{a.description && <p className="mt-0.5 line-clamp-2 text-xs text-muted">{a.description}</p>}
<p className="mt-1 text-xs text-faint">
{a.pending_count ?? 0}/{a.max_pending_requests} pending ·{" "}
<code>{a.api_key_masked}</code>
</p>
</div>
</div>
<div className="mt-4 flex flex-wrap gap-2">
<Button variant="secondary" onClick={() => setConnectAgent(a)}>
Connect
</Button>
<Button
variant="ghost"
onClick={() => {
setEditing(a);
setFormOpen(true);
}}
>
Edit
</Button>
<Button variant="ghost" onClick={() => regenerate(a)}>
Regenerate key
</Button>
<Button variant="ghost" onClick={() => toggleDisabled(a)}>
{a.disabled ? "Enable" : "Disable"}
</Button>
<Button variant="ghost" className="text-rejected" onClick={() => remove(a)}>
Delete
</Button>
</div>
</Card>
))}
</div>
)}
<AgentFormModal
open={formOpen}
onClose={() => setFormOpen(false)}
agent={editing}
onSaved={onSaved}
/>
{connectAgent && (
<ConnectModal
open={!!connectAgent}
onClose={() => {
setConnectAgent(null);
setRevealedKey(null);
}}
agent={connectAgent}
revealedKey={revealedKey}
/>
)}
</div>
);
}