diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..93e4a1e --- /dev/null +++ b/.env.example @@ -0,0 +1,23 @@ +# ───────────────────────────────────────────── +# GoClaw Control Center — Environment Variables +# Скопируйте этот файл в .env и заполните значения +# ───────────────────────────────────────────── + +# Ollama API (обязательно для работы чата и списка моделей) +OLLAMA_BASE_URL=https://ollama.com/v1 +OLLAMA_API_KEY=your_ollama_api_key_here + +# База данных MySQL/TiDB +DATABASE_URL=mysql://goclaw:password@localhost:3306/goclaw + +# JWT Secret — случайная строка для подписи сессионных токенов +# Сгенерировать: openssl rand -hex 32 +JWT_SECRET=change_me_to_random_secret + +# Telegram Bot (опционально) +TELEGRAM_BOT_TOKEN= +TELEGRAM_WEBHOOK_URL= + +# GoClaw Gateway (опционально) +GATEWAY_URL=http://localhost:18789 +GATEWAY_API_KEY= diff --git a/README.md b/README.md new file mode 100644 index 0000000..c87fd43 --- /dev/null +++ b/README.md @@ -0,0 +1,210 @@ +# GoClaw Control Center + +> **Mission Control для вашего AI-агентского кластера.** Веб-интерфейс для мониторинга Docker Swarm, управления AI-агентами и общения с оркестратором через реальный LLM (Ollama Cloud API). + +--- + +## Запуск одной командой + +```bash +curl -fsSL https://git.softuniq.eu/UniqAI/GoClaw/raw/branch/main/install.sh | bash +``` + +Или вручную через Docker Compose: + +```bash +git clone https://git.softuniq.eu/UniqAI/GoClaw.git && cd GoClaw && cp .env.example .env && docker compose up -d +``` + +После запуска откройте: **http://localhost:3000** + +--- + +## Быстрый старт (без Docker) + +```bash +# 1. Клонировать репозиторий +git clone https://git.softuniq.eu/UniqAI/GoClaw.git +cd GoClaw + +# 2. Установить зависимости +pnpm install + +# 3. Настроить переменные окружения +cp .env.example .env +# Отредактируйте .env — укажите OLLAMA_BASE_URL и OLLAMA_API_KEY + +# 4. Запустить базу данных +pnpm db:push + +# 5. Запустить dev-сервер +pnpm dev +``` + +--- + +## Архитектура + +``` +GoClaw Control Center +├── client/ # React 19 + Tailwind 4 + shadcn/ui (фронтенд) +│ └── src/ +│ ├── pages/ # Dashboard, Agents, Nodes, Chat, Settings +│ └── components/ # DashboardLayout, UI-компоненты +├── server/ # Express 4 + tRPC 11 (бэкенд) +│ ├── ollama.ts # Прокси-клиент для Ollama API +│ ├── routers.ts # tRPC роуты (ollama.health, models, chat) +│ └── _core/ # Auth, DB, LLM, Storage хелперы +├── drizzle/ # Схема БД и миграции (MySQL/TiDB) +├── docker/ # Docker Stack для Swarm-деплоя +│ ├── docker-stack.yml # Полный стек: Gateway + Agents + Control Center +│ ├── Dockerfile.gateway # Образ основного шлюза GoClaw +│ ├── Dockerfile.agent # Образ AI-агента +│ └── Dockerfile.control-center # Образ веб-интерфейса (nginx) +└── docs/ # Документация и спецификации + ├── swarm_architecture.md + └── goclaw_swarm_cloud_guide.md +``` + +--- + +## Возможности + +| Раздел | Что умеет | +| :--- | :--- | +| **Dashboard** | Мониторинг Swarm-нод, статус агентов, лента активности, live-статус Ollama API | +| **Agents** | Просмотр всех AI-агентов с их ролями, моделями и текущими задачами | +| **Nodes** | Мониторинг Docker Swarm нод: CPU, RAM, контейнеры, статус | +| **Chat** | Терминальный чат с оркестратором через реальный LLM (34 модели Ollama) | +| **Settings** | Управление API-провайдерами, сканирование доступных моделей, настройки Telegram | + +--- + +## Переменные окружения + +Скопируйте `.env.example` в `.env` и заполните: + +```env +# Ollama API (обязательно) +OLLAMA_BASE_URL=https://ollama.com/v1 +OLLAMA_API_KEY=your_api_key_here + +# База данных (обязательно для production) +DATABASE_URL=mysql://user:password@host:3306/goclaw + +# JWT (сгенерируйте случайную строку) +JWT_SECRET=your_random_secret_here + +# Telegram Bot (опционально) +TELEGRAM_BOT_TOKEN=your_bot_token +``` + +--- + +## Docker Swarm деплой + +```bash +# Инициализировать Swarm (если ещё не сделано) +docker swarm init + +# Задать секреты +export OLLAMA_API_KEY=your_key +export DATABASE_URL=mysql://... +export JWT_SECRET=your_secret + +# Развернуть стек +docker stack deploy -c docker/docker-stack.yml goclaw + +# Проверить статус +docker stack services goclaw +``` + +Добавление новой ноды в кластер: +```bash +# На manager-ноде получить токен +docker swarm join-token worker + +# На новой ноде выполнить команду из вывода выше +docker swarm join --token SWMTKN-... manager-ip:2377 +``` + +--- + +## Технологический стек + +| Слой | Технологии | +| :--- | :--- | +| **Фронтенд** | React 19, Tailwind CSS 4, shadcn/ui, Framer Motion, tRPC Client | +| **Бэкенд** | Node.js, Express 4, tRPC 11, Drizzle ORM, Zod | +| **База данных** | MySQL / TiDB (через Drizzle) | +| **LLM** | Ollama Cloud API (OpenAI-совместимый, 34+ модели) | +| **Оркестрация** | Docker Swarm, Overlay Network | +| **Агенты (Go)** | gRPC, Docker SDK, Goroutines, Channels | +| **Тесты** | Vitest | + +--- + +## Структура GoClaw Swarm + +``` + ┌─────────────────────────────────┐ + │ goclaw-net (Overlay Network) │ + └─────────────────────────────────┘ + │ + ┌─────────────────────┼─────────────────────┐ + │ │ │ + ┌─────────▼──────┐ ┌──────────▼──────┐ ┌─────────▼──────┐ + │ Control Center │ │ Gateway │ │ Agents │ + │ (Web UI :3000) │ │ (Orchestrator) │ │ (Docker Svc) │ + │ React + tRPC │ │ Go + gRPC │ │ Coder/Browser │ + └────────────────┘ └─────────────────┘ │ Mail/Monitor │ + │ └─────────────────┘ + ┌───────────▼──────────┐ + │ Ollama Cloud API │ + │ (34 LLM Models) │ + └──────────────────────┘ +``` + +--- + +## Разработка + +```bash +# Запустить тесты +pnpm test + +# Проверить типы +pnpm check + +# Форматировать код +pnpm format + +# Применить миграции БД +pnpm db:push +``` + +--- + +## Дорожная карта + +- [x] Dashboard с мониторингом кластера +- [x] Реальная интеграция Ollama API (34 модели) +- [x] Терминальный чат с LLM +- [x] Docker Swarm Stack +- [x] gRPC API для агентов (Go) +- [ ] Стриминг ответов LLM в чате +- [ ] CRUD агентов через UI +- [ ] Подключение реального Docker API для нод +- [ ] Telegram-коннектор +- [ ] Система скиллов (Self-Evolution) +- [ ] Аутентификация и RBAC + +--- + +## Лицензия + +MIT — используйте свободно для личных и коммерческих проектов. + +--- + +*Разработано в рамках проекта **GoClaw** — распределённой системы AI-агентов на Go + Docker Swarm.* diff --git a/client/src/components/AgentCreateModal.test.ts b/client/src/components/AgentCreateModal.test.ts new file mode 100644 index 0000000..5cc8a99 --- /dev/null +++ b/client/src/components/AgentCreateModal.test.ts @@ -0,0 +1,91 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { AgentCreateModal } from "./AgentCreateModal"; + +// Mock trpc +vi.mock("@/lib/trpc", () => ({ + trpc: { + ollama: { + models: { + useQuery: () => ({ + data: { + models: [ + { id: "deepseek-v3.2", provider: "Ollama" }, + { id: "gpt-4", provider: "OpenAI" }, + ], + }, + isLoading: false, + }), + }, + }, + agents: { + create: { + useMutation: () => ({ + mutateAsync: vi.fn().mockResolvedValue({}), + isPending: false, + }), + }, + }, + }, +})); + +describe("AgentCreateModal", () => { + it("should render create modal when open is true", () => { + render( + {}} + /> + ); + expect(screen.getByText(/Deploy New Agent/)).toBeInTheDocument(); + }); + + it("should have required form fields", () => { + render( + {}} + /> + ); + expect(screen.getByLabelText(/Agent Name/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Description/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Role/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Provider/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Model/)).toBeInTheDocument(); + }); + + it("should have LLM parameter controls", () => { + render( + {}} + /> + ); + expect(screen.getByLabelText(/Temperature/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Max Tokens/)).toBeInTheDocument(); + expect(screen.getByLabelText(/System Prompt/)).toBeInTheDocument(); + }); + + it("should disable create button when name is empty", () => { + render( + {}} + /> + ); + const createButton = screen.getByRole("button", { name: /Create Agent/i }); + expect(createButton).toBeDisabled(); + }); + + it("should have cancel and create buttons", () => { + render( + {}} + /> + ); + expect(screen.getByRole("button", { name: /Cancel/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Create Agent/i })).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/AgentCreateModal.tsx b/client/src/components/AgentCreateModal.tsx new file mode 100644 index 0000000..1917e5f --- /dev/null +++ b/client/src/components/AgentCreateModal.tsx @@ -0,0 +1,257 @@ +import { useState } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { trpc } from "@/lib/trpc"; +import { toast } from "sonner"; +import { Loader2, Plus } from "lucide-react"; + +interface AgentCreateModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess?: () => void; +} + +const AGENT_ROLES = [ + { value: "developer", label: "Developer - Code generation & testing" }, + { value: "researcher", label: "Researcher - Data analysis & research" }, + { value: "executor", label: "Executor - Task automation" }, + { value: "monitor", label: "Monitor - System monitoring" }, +]; + +const PROVIDERS = [ + { value: "Ollama", label: "Ollama (Local/Cloud)" }, + { value: "OpenAI", label: "OpenAI (GPT)" }, + { value: "Anthropic", label: "Anthropic (Claude)" }, +]; + +export function AgentCreateModal({ open, onOpenChange, onSuccess }: AgentCreateModalProps) { + const [formData, setFormData] = useState({ + name: "", + description: "", + role: "developer", + provider: "Ollama", + model: "deepseek-v3.2", + temperature: 0.7, + maxTokens: 2048, + systemPrompt: "", + }); + + const [isLoading, setIsLoading] = useState(false); + + const { data: models } = trpc.ollama.models.useQuery(); + + const createMutation = trpc.agents.create.useMutation({ + onSuccess: () => { + toast.success("Agent created successfully"); + setFormData({ + name: "", + description: "", + role: "developer", + provider: "Ollama", + model: "deepseek-v3.2", + temperature: 0.7, + maxTokens: 2048, + systemPrompt: "", + }); + onOpenChange(false); + onSuccess?.(); + }, + onError: (error) => { + toast.error(`Failed to create agent: ${error.message}`); + }, + }); + + const handleCreate = async () => { + if (!formData.name.trim()) { + toast.error("Agent name is required"); + return; + } + + setIsLoading(true); + try { + await createMutation.mutateAsync({ + name: formData.name, + description: formData.description, + role: formData.role, + provider: formData.provider, + model: formData.model, + temperature: formData.temperature, + maxTokens: formData.maxTokens, + systemPrompt: formData.systemPrompt, + allowedTools: [], + }); + } finally { + setIsLoading(false); + } + }; + + const availableModels = models?.models + ?.filter((m: any) => m.provider === formData.provider || formData.provider === "Ollama") + .map((m: any) => m.id) || []; + + return ( + + + + Deploy New Agent + + +
+ {/* Basic Info */} +
+ + setFormData({ ...formData, name: e.target.value })} + className="font-mono" + /> +
+ +
+ +