Large World graph, agent platform, brain stack & dashboard rebuild

Frontend
- Large World: collapse org/company/team tiers into one expandable React Flow
  hierarchy (WorldFlow) with per-click expand, persisted node positions, a
  compact tree sidebar, wrench multi-select delete across levels, and a sized
  right slide-out (phone/tablet/full) showing an agent summary + drill button.
- Agent page: GitHub-style animated contribution grid (VitalsCard), collapsible
  System Prompt + Personality cards, restructured anatomy cards, bigger avatar
  with name/title header row, Markdown/JSON-aware rendering, brain registry +
  history, avatar generate/upload.
- User-icon menu (Infrastructure/Brains/Tools/Profile/Credits) + ToolPanel;
  Master Planner deploy wizard (Specialists/Swarm/Scheduled/Triggered);
  Team Runs view; reap-progress modal; dashboard is the single live interface.

Backend
- cm-brain crate (.brain as the agent definition) + brain apply/history.
- Hard-purge reap (FK-ordered) + sandbox release + SSE batch-delete.
- Swarm self-verifying loop, mode-aware planner, web.search tool, webhooks
  (migration 0013), org/company/team delete endpoints, scheduler sweeps.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-22 23:21:54 -07:00
co-authored by Claude Opus 4.8
parent 9f266d5806
commit 34f744734b
123 changed files with 9591 additions and 1098 deletions
@@ -0,0 +1,62 @@
// Local Next route (NOT proxied — a specific path beats the /api/[...path]
// catch-all): generates an agent avatar with Gemini's image model ("Nano
// Banana", gemini-2.5-flash-image) using GEMINI_API_KEY from the frontend env,
// and returns a base64 data URL. Saving the chosen image is a separate
// PATCH /api/claws/{id} {avatar} (the existing backend route).
import { NextResponse, type NextRequest } from "next/server";
import { resolveBearer } from "@/lib/auth/bearer";
const MODEL = "gemini-2.5-flash-image";
const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent`;
interface InlineData { data?: string; mimeType?: string; mime_type?: string }
interface GeminiPart { inlineData?: InlineData; inline_data?: InlineData }
interface GeminiResponse { candidates?: Array<{ content?: { parts?: GeminiPart[] } }> }
export async function POST(request: NextRequest) {
const token = await resolveBearer();
if (!token) return NextResponse.json({ error: "unauthenticated" }, { status: 401 });
const key = process.env.GEMINI_API_KEY;
if (!key) return NextResponse.json({ error: "image generation is not configured (set GEMINI_API_KEY)" }, { status: 503 });
let prompt = "";
try {
const body = (await request.json()) as { prompt?: unknown };
prompt = String(body?.prompt ?? "").trim();
} catch {
/* fall through to the 400 below */
}
if (!prompt) return NextResponse.json({ error: "prompt required" }, { status: 400 });
let upstream: Response;
try {
upstream = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", "x-goog-api-key": key },
body: JSON.stringify({
contents: [{ parts: [{ text: `A clean, centered square avatar portrait for an AI agent. ${prompt}` }] }],
generationConfig: { responseModalities: ["IMAGE"] },
}),
});
} catch {
return NextResponse.json({ error: "could not reach the image service" }, { status: 502 });
}
if (!upstream.ok) {
const detail = await upstream.text().catch(() => "");
return NextResponse.json({ error: `image service error (${upstream.status})`, detail: detail.slice(0, 400) }, { status: 502 });
}
const data = (await upstream.json().catch(() => null)) as GeminiResponse | null;
const parts = data?.candidates?.[0]?.content?.parts ?? [];
const part = parts.find((p) => p.inlineData?.data || p.inline_data?.data);
const inline = part?.inlineData ?? part?.inline_data;
if (!inline?.data) {
return NextResponse.json({ error: "the model did not return an image — try a different prompt" }, { status: 502 });
}
const mime = inline.mimeType ?? inline.mime_type ?? "image/png";
return NextResponse.json({ image: `data:${mime};base64,${inline.data}` });
}