import { useState, useEffect } from "react"; import { Info, Settings, Terminal, X, Cpu, Users, BookOpen, Search, Sparkles, Bot, Image, Box, Film } from "lucide-react"; import type { LoraCheckpoint, ProjectModeData } from "../../types"; import { GenerateTab } from "./GenerateTab"; import { NodesTab } from "./NodesTab"; import { ProjectSidebar } from "./ProjectSidebar"; import { ProjectsGuide } from "../ProjectsGuide"; export type SidebarTab = "generate" | "characters" | "agents" | "projects" | "guide" | "info" | "nodes" | "settings" | "dev"; export const SIDEBAR_WIDTH = 380; interface CharacterSummary { id: string; name: string; trigger: string | null; kind: string | null; loras: Record[]; source_images: string[]; defaults: Record; } interface AppSidebarProps { activeTab: SidebarTab; onTabChange: (tab: SidebarTab) => void; onClose: () => void; checkpoints: LoraCheckpoint[]; onQueued?: () => void; onSelectCharacter?: (characterId: string) => void; projectMode?: ProjectModeData; } export function AppSidebar({ activeTab, onTabChange, onClose, checkpoints, onQueued, onSelectCharacter, projectMode }: AppSidebarProps) { const topTabs: { id: SidebarTab; icon: React.ReactNode; label: string; visible: boolean }[] = [ { id: "generate", icon: , label: "Generate", visible: true }, { id: "characters", icon: , label: "Characters & LoRA Training", visible: true }, { id: "agents", icon: , label: "Agents", visible: true }, { id: "projects", icon: , label: "Projects", visible: true }, { id: "guide", icon: , label: "Guide", visible: true }, { id: "info", icon: , label: "Info", visible: true }, { id: "nodes", icon: , label: "Nodes", visible: true }, ]; const bottomTabs: { id: SidebarTab; icon: React.ReactNode; label: string; visible: boolean }[] = [ { id: "dev", icon: , label: "Logs", visible: true }, { id: "settings", icon: , label: "Settings", visible: true }, ]; const visibleTopTabs = topTabs.filter((tab) => tab.visible); const visibleBottomTabs = bottomTabs.filter((tab) => tab.visible); const visibleTabs = [...visibleTopTabs, ...visibleBottomTabs]; return (
{/* Icon rail */}
{visibleTopTabs.map((tab) => ( ))}
{visibleBottomTabs.map((tab) => ( ))}
{/* Content panel */}
{projectMode && activeTab === "projects" ? "Scenes" : visibleTabs.find((tab) => tab.id === activeTab)?.label}
{activeTab === "characters" && } {activeTab === "generate" && } {activeTab === "agents" && } {activeTab === "projects" && (projectMode ? projectMode.onDeleteScene(id)} /> : )} {activeTab === "guide" && } {activeTab === "info" && } {activeTab === "nodes" && } {activeTab === "settings" && } {activeTab === "dev" && }
); } function CharactersTab({ onSelectCharacter }: { onSelectCharacter?: (characterId: string) => void }) { const [characters, setCharacters] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetch("/api/characters") .then((r) => r.json()) .then((d) => setCharacters(d.characters || [])) .catch((e) => setError(e.message)) .finally(() => setLoading(false)); }, []); return (
{/* Character cards first — your owned assets */}

Your character assets

Owned

These are characters you created or own the rights to use. Each one is backed by a fine-tuned LoRA trained on AMD MI300X.

{loading &&

Loading...

} {error &&

{error}

} {!loading && characters.length === 0 && (

No characters registered yet.

)} {characters.map((ch) => { const avatarUrl = ch.source_images.length > 0 ? (ch.source_images[0].startsWith("/") ? ch.source_images[0] : `/media/${ch.source_images[0]}`) : null; return (
onSelectCharacter?.(ch.id)} >
{avatarUrl ? ( {ch.name} ) : (
{ch.name.charAt(0).toUpperCase()}
)}
{ch.name} {ch.kind === "human" && ( Human )} {ch.kind === "agent" && ( Agent )}
{ch.id} {ch.loras.length > 0 && ( {ch.loras.length} LoRA )} Owned

Available for your agent to use in generated images and videos.

); })} {/* Training workflow below the character card */}
); } function CreateCharacterWorkflow() { return (
LoRA Fine-tuning AMD MI300X

Train your own character LoRA in ~90 minutes on AMD MI300X. Once fine-tuned, your AI agent can generate consistent images and videos with your face — every single time.

AMD MI300X — 192 GB VRAM

Fine-tuning runs on AMD's flagship GPU via ROCm. No CUDA required. Train a Flux2 LoRA in ~90 minutes, then generate images and videos immediately.

Tell your AI agent:

"Create a character for me. Upload my reference images, register the character, start a LoRA fine-tune on AMD MI300X, and let me know when it's ready to use."

How it works:

{[ { n: 1, title: "Upload your images", body: "5-20 reference photos. Different angles and lighting work best. Your agent can even generate variations to build a dataset." }, { n: 2, title: "Register your character", body: "A character record with a unique trigger word — this is how the agent references your identity in every generation." }, { n: 3, title: "Fine-tune on AMD MI300X", body: "Training a Flux2 LoRA on 192 GB MI300X VRAM via ROCm. Takes about 90 minutes. Your agent monitors progress and notifies you when done.", highlight: true }, { n: 4, title: "Generate consistently", body: 'Your character appears in the registry. From then on, just say "generate a shot with [character name] doing X" — your agent handles the rest.' }, ].map((step) => (
{step.n}

{step.title}

{step.body}

))}

Your agent knows the API. It uses /api/characters to register, /api/lora-training to fine-tune, and the Guide tab has all the curl commands.

); } function GuideTab() { const apiRoot = typeof window !== "undefined" ? window.location.origin : "http://127.0.0.1:8190"; const examples = [ { label: "List characters", cmd: `curl ${apiRoot}/api/characters` }, { label: "Create character", cmd: `curl -X POST ${apiRoot}/api/characters \\ -H "Content-Type: application/json" \\ -d '{"id":"my-char","name":"My Character","trigger":"MyTrigger"}'` }, { label: "Create project", cmd: `curl -X POST ${apiRoot}/api/projects \\ -H "Content-Type: application/json" \\ -d '{"title":"My First Short","content":"A brief story...","characters":["rigo"]}'` }, { label: "Add scene to project", cmd: `curl -X POST ${apiRoot}/api/projects//scenes \\ -H "Content-Type: application/json" \\ -d '{"scene_number":1,"heading":"INT.-DAY","summary":"Scene summary"}'` }, { label: "Add shot to scene", cmd: `curl -X POST ${apiRoot}/api/projects//scenes//shots \\ -H "Content-Type: application/json" \\ -d '{"shot_number":1,"description":"Visual shot description","motion_prompt":"Camera push in","duration_seconds":4}'` }, { label: "Generate shot image", cmd: `curl -X POST ${apiRoot}/api/projects//scenes//shots//generate-image` }, { label: "Animate shot", cmd: `curl -X POST ${apiRoot}/api/projects//scenes//shots//animate` }, { label: "View project", cmd: `curl ${apiRoot}/api/projects/` }, { label: "Raw image generation", cmd: `curl -X POST ${apiRoot}/api/image/generate \\ -H "Content-Type: application/json" \\ -d '{"character":"rigo","prompt":"studio portrait"}'` }, { label: "Raw video generation", cmd: `curl -X POST ${apiRoot}/api/video/generate \\ -H "Content-Type: application/json" \\ -d '{"character":"rigo","prompt":"walks forward","width":1024,"height":1024,"length":41}'` }, ]; return (

Paste these into a terminal or send them to your agent. Replace <prj_id> / <scn_id> / <sht_id> with actual IDs.

{examples.map((ex) => (

{ex.label}

{ex.cmd}
))}
); } function AgentsTab() { const capabilities = [ "Create and manage agent profiles", "Register owned character assets", "Start image and video generation jobs", "Build project scenes and shots through the API", "Route work to configured GPU nodes", "Launch LoRA training with ai-toolkit", ]; const workflow = [ { n: "01", title: "Tell the agent what to make", body: "Use plain language instead of filling out every workflow setting yourself.", }, { n: "02", title: "Agent chooses the right workflow", body: "Image, video, character creation, project planning, or LoRA training should be selected automatically.", }, { n: "03", title: "Nemoflix runs the job", body: "The backend handles API calls, configured GPU nodes, output paths, and job tracking.", }, { n: "04", title: "Review and continue", body: "Generated assets appear in the Studio so the agent and human can iterate together.", }, ]; return (

AI Agents

Coming soon

Nemoflix is designed for AI agents that create media, manage characters, generate scenes, and publish work through an API-first studio.

How agents use Nemoflix

{workflow.map((step) => (
{step.n}

{step.title}

{step.body}

))}

Planned capabilities

{capabilities.map((item) => (
{item}
))}

Backend direction

The agent endpoint should eventually execute real Nemoflix tools behind the scenes: character lookup, generation queueing, project updates, node checks, and training jobs.

); } function PlaceholderTab({ title, body }: { title: string; body: string }) { return (

{title}

{body}

); }