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:
co-authored by
Claude Opus 4.8
parent
9f266d5806
commit
34f744734b
@@ -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}` });
|
||||
}
|
||||
Reference in New Issue
Block a user