From 7c02c6d213ec7d728b70ea3bdc8fe662a1770303 Mon Sep 17 00:00:00 2001 From: Markov Date: Mon, 23 Feb 2026 10:53:55 +0100 Subject: [PATCH] feat: agent management page --- src/app/(protected)/agents/page.tsx | 96 +++++++++++++++++ src/components/CreateAgentModal.tsx | 153 ++++++++++++++++++++++++++++ src/components/Sidebar.tsx | 12 ++- 3 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 src/app/(protected)/agents/page.tsx create mode 100644 src/components/CreateAgentModal.tsx diff --git a/src/app/(protected)/agents/page.tsx b/src/app/(protected)/agents/page.tsx new file mode 100644 index 0000000..4e5624d --- /dev/null +++ b/src/app/(protected)/agents/page.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { getMembers, Member, MemberCreateResponse } from "@/lib/api"; +import CreateAgentModal from "@/components/CreateAgentModal"; + +export default function AgentsPage() { + const [agents, setAgents] = useState([]); + const [loading, setLoading] = useState(true); + const [showModal, setShowModal] = useState(false); + + const load = async () => { + try { + const members = await getMembers(); + setAgents(members.filter((m) => m.type === "agent")); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + load(); + }, []); + + const handleCreated = (_member: MemberCreateResponse) => { + load(); + }; + + return ( +
+
+

🤖 Агенты

+ +
+ + {loading ? ( +
Загрузка...
+ ) : agents.length === 0 ? ( +
Нет агентов
+ ) : ( +
+ {agents.map((agent) => ( +
+
+

{agent.name}

+ + {agent.status === "online" ? "online" : "offline"} + +
+
@{agent.slug}
+ + {agent.agent_config?.capabilities && agent.agent_config.capabilities.length > 0 && ( +
+ {agent.agent_config.capabilities.map((cap) => ( + + {cap} + + ))} +
+ )} + +
+
💬 chat: {agent.agent_config?.chat_listen || "—"}
+
📋 tasks: {agent.agent_config?.task_listen || "—"}
+
+
+ ))} +
+ )} + + {showModal && ( + setShowModal(false)} + /> + )} +
+ ); +} diff --git a/src/components/CreateAgentModal.tsx b/src/components/CreateAgentModal.tsx new file mode 100644 index 0000000..24ebc9b --- /dev/null +++ b/src/components/CreateAgentModal.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { useState } from "react"; +import { createMember, MemberCreateResponse } from "@/lib/api"; + +interface Props { + onCreated: (member: MemberCreateResponse) => void; + onClose: () => void; +} + +export default function CreateAgentModal({ onCreated, onClose }: Props) { + const [name, setName] = useState(""); + const [capabilities, setCapabilities] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [token, setToken] = useState(null); + const [copied, setCopied] = useState(false); + + const slug = name + .toLowerCase() + .replace(/[^a-zа-яё0-9\s-]/gi, "") + .replace(/\s+/g, "-") + .replace(/-+/g, "-") + .slice(0, 50); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!name.trim() || !slug) return; + setLoading(true); + setError(""); + try { + const caps = capabilities + .split(",") + .map((c) => c.trim()) + .filter(Boolean); + const member = await createMember({ + name: name.trim(), + slug, + type: "agent", + agent_config: { + capabilities: caps, + chat_listen: "mentions", + task_listen: "assigned", + }, + }); + if (member.token) { + setToken(member.token); + } + onCreated(member); + } catch { + setError("Ошибка создания агента"); + } finally { + setLoading(false); + } + }; + + const copyToken = () => { + if (token) { + navigator.clipboard.writeText(token); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + if (token) { + return ( +
+
e.stopPropagation()} + className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-6 w-96" + > +

Агент создан

+
+ ⚠️ Токен показывается только один раз. Сохраните его сейчас! +
+
+ {token} +
+
+ + +
+
+
+ ); + } + + return ( +
+
e.stopPropagation()} + onSubmit={handleSubmit} + className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-6 w-96" + > +

Новый агент

+ + {error && ( +
+ {error} +
+ )} + + + setName(e.target.value)} + className="w-full mb-1 px-3 py-2 bg-[var(--bg)] border border-[var(--border)] rounded-lg + outline-none focus:border-[var(--accent)] text-sm" + placeholder="Code Assistant" + /> + {slug &&
Slug: {slug}
} + + + setCapabilities(e.target.value)} + className="w-full mb-4 px-3 py-2 bg-[var(--bg)] border border-[var(--border)] rounded-lg + outline-none focus:border-[var(--accent)] text-sm" + placeholder="code, review, deploy" + /> + +
+ + +
+
+
+ ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 33b7370..7415e86 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -39,6 +39,16 @@ export default function Sidebar({ projects, activeSlug }: Props) { Новый проект + setOpen(false)} + className="flex items-center gap-2 px-3 py-2 mb-2 rounded text-sm text-[var(--fg)] + hover:bg-white/5 transition-colors" + > + 🤖 + Агенты + +
Проекты
{projects.map((p) => (
- Team Board v0.1 + Team Board v0.2