Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Implementation:Microsoft Autogen Studio Gallery Detail

From Leeroopedia
Knowledge Sources python/packages/autogen-studio/frontend/src/components/views/gallery/detail.tsx
Domains Frontend, UI Components, Gallery Management, Component Editing
Last Updated 2026-02-11

Overview

The Gallery Detail component provides a comprehensive interface for viewing, editing, and managing component galleries in AutoGen Studio, including visual component browsers, JSON editor, and inline component editing capabilities.

Description

This React component implements the main gallery detail view with extensive management capabilities. It includes:

Visual Features:

  • Banner header with gallery metadata (name, description, version, component count)
  • Tabbed interface for component categories (Teams, Agents, Models, Tools, Workbenches, Terminations)
  • Responsive grid layout for component cards (1-4 columns based on viewport)
  • Component cards with hover actions (edit, duplicate, delete)
  • Component count badges in tab headers
  • Download gallery as JSON functionality

Editing Capabilities:

  • Dual Editor Modes: Form-based component editor and raw JSON editor
  • Inline Details Editing: Edit gallery name and description in place
  • Component CRUD Operations: Create, read, update, delete components within categories
  • Component Editor Drawer: Full-featured side drawer for editing individual components
  • Template-Based Creation: Add new components from pre-defined templates via dropdown
  • Duplicate Components: Smart duplication with automatic label incrementing

State Management:

  • Working copy management for JSON editing mode
  • Dirty state tracking for unsaved changes
  • Optimistic UI updates with save callbacks
  • Integration with parent component via onSave and onDirtyStateChange callbacks

Component Organization:

  • Category-based organization (teams, agents, models, tools, workbenches, terminations)
  • Icon-based visual identification for each category
  • Count tracking for each component type
  • Minimum component enforcement (prevents deletion if only one remains)

The component uses Ant Design for UI elements, Monaco Editor for JSON editing, and custom ComponentEditor for structured component editing. It manages complex state including active tab, editing states, working copies, and unsaved changes while maintaining synchronization with the parent component.

Usage

This component is used in the Gallery view when a user selects a gallery to view or edit. It receives a gallery object and callbacks for saving changes and tracking dirty state.

import { GalleryDetail } from "./views/gallery/detail";
import type { Gallery } from "./types/datamodel";

function GalleryPage() {
  const [gallery, setGallery] = useState<Gallery>(/* ... */);
  const [isDirty, setIsDirty] = useState(false);

  return (
    <GalleryDetail
      gallery={gallery}
      onSave={(updates) => {
        setGallery({ ...gallery, ...updates });
        saveToBackend(updates);
      }}
      onDirtyStateChange={setIsDirty}
    />
  );
}

Code Reference

Source Location: /tmp/kapso_repo_2mr4n2g4/python/packages/autogen-studio/frontend/src/components/views/gallery/detail.tsx

Component Signature:

export const GalleryDetail: React.FC<{
  gallery: Gallery;
  onSave: (updates: Partial<Gallery>) => void;
  onDirtyStateChange: (isDirty: boolean) => void;
}> = ({ gallery, onSave, onDirtyStateChange }) => {
  // Component implementation
}

Import Statement:

import { GalleryDetail } from "./views/gallery/detail";
import type { Gallery } from "./types/datamodel";

Key Internal Types:

type CategoryKey =
  | "teams"
  | "agents"
  | "models"
  | "tools"
  | "workbenches"
  | "terminations";

interface CardActions {
  onEdit: (component: Component<ComponentConfig>, index: number) => void;
  onDuplicate: (component: Component<ComponentConfig>, index: number) => void;
  onDelete: (component: Component<ComponentConfig>, index: number) => void;
}

Sub-Components:

// Component card displaying individual component
const ComponentCard: React.FC<CardActions & {
  item: Component<ComponentConfig>;
  index: number;
  allowDelete: boolean;
  disabled?: boolean;
}>;

// Grid layout for component cards
const ComponentGrid: React.FC<{
  items: Component<ComponentConfig>[];
  title: string;
  disabled?: boolean;
} & CardActions>;

I/O Contract

Inputs

Parameter Type Required Description
gallery Gallery Yes Gallery object containing all components and metadata
onSave (updates: Partial<Gallery>) => void Yes Callback invoked when gallery changes are saved
onDirtyStateChange (isDirty: boolean) => void Yes Callback to notify parent of unsaved changes state

Outputs

Type Description
JSX.Element Rendered gallery detail interface with all management capabilities

Side Effects

  • Calls onSave callback when components are added, edited, duplicated, or deleted
  • Calls onDirtyStateChange when user makes modifications
  • Displays toast messages (via Ant Design message API) for save success/errors
  • Downloads JSON file when user clicks download button
  • Opens drawer for component editing
  • Manages URL object lifecycle when creating download links

State Management

State Variable Type Description
editingComponent null Currently edited component with category and index
activeTab ComponentTypes Currently active tab (team, agent, model, etc.)
isEditingDetails boolean Whether gallery name/description is being edited
isJsonEditing boolean Whether in JSON editor mode vs. form mode
workingCopy Gallery Working copy of gallery for JSON editing
jsonValue string Raw JSON string in JSON editor
hasUnsavedChanges boolean Whether there are unsaved changes
isDirty boolean Whether JSON editor has modifications
tempName string Temporary gallery name during inline editing
tempDescription string Temporary gallery description during inline editing

Usage Examples

Basic Integration

import { GalleryDetail } from "./views/gallery/detail";
import type { Gallery } from "./types/datamodel";

function GalleryManager() {
  const [gallery, setGallery] = useState<Gallery>({
    id: 1,
    config: {
      id: "my-gallery",
      name: "My Gallery",
      metadata: {
        author: "User",
        version: "1.0.0",
        description: "Custom components",
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString()
      },
      components: {
        teams: [],
        agents: [],
        models: [],
        tools: [],
        workbenches: [],
        terminations: []
      }
    }
  });

  const [isDirty, setIsDirty] = useState(false);

  const handleSave = (updates: Partial<Gallery>) => {
    const updatedGallery = { ...gallery, ...updates };
    setGallery(updatedGallery);

    // Save to backend
    fetch(`/api/galleries/${gallery.id}`, {
      method: "PATCH",
      body: JSON.stringify(updatedGallery),
      headers: { "Content-Type": "application/json" }
    });
  };

  return (
    <div>
      {isDirty && <div className="unsaved-warning">Unsaved changes</div>}
      <GalleryDetail
        gallery={gallery}
        onSave={handleSave}
        onDirtyStateChange={setIsDirty}
      />
    </div>
  );
}

With Navigation Guard

import { useEffect } from "react";
import { Prompt } from "react-router-dom";

function GalleryPage() {
  const [gallery, setGallery] = useState<Gallery>(/* ... */);
  const [isDirty, setIsDirty] = useState(false);

  // Warn user before leaving with unsaved changes
  useEffect(() => {
    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
      if (isDirty) {
        e.preventDefault();
        e.returnValue = "";
      }
    };

    window.addEventListener("beforeunload", handleBeforeUnload);
    return () => window.removeEventListener("beforeunload", handleBeforeUnload);
  }, [isDirty]);

  return (
    <>
      <Prompt
        when={isDirty}
        message="You have unsaved changes. Are you sure you want to leave?"
      />
      <GalleryDetail
        gallery={gallery}
        onSave={(updates) => {
          setGallery({ ...gallery, ...updates });
          setIsDirty(false);
        }}
        onDirtyStateChange={setIsDirty}
      />
    </>
  );
}

Accessing Specific Components

The component internally handles all CRUD operations. External code can access components via the gallery prop:

function analyzeGallery(gallery: Gallery) {
  // Count components by type
  const counts = {
    teams: gallery.config.components.teams.length,
    agents: gallery.config.components.agents.length,
    models: gallery.config.components.models.length,
    tools: gallery.config.components.tools.length,
    workbenches: gallery.config.components.workbenches.length,
    terminations: gallery.config.components.terminations.length
  };

  // Find specific components
  const assistants = gallery.config.components.agents.filter(
    agent => isAssistantAgent(agent)
  );

  const openAIModels = gallery.config.components.models.filter(
    model => isOpenAIModel(model)
  );

  return { counts, assistants, openAIModels };
}

Custom Save Handler with Validation

function ValidatedGalleryPage() {
  const [gallery, setGallery] = useState<Gallery>(/* ... */);

  const handleSave = async (updates: Partial<Gallery>) => {
    try {
      const updatedGallery = { ...gallery, ...updates };

      // Validate gallery structure
      if (!validateGallery(updatedGallery)) {
        message.error("Invalid gallery configuration");
        return;
      }

      // Check for required API keys in tools
      const missingKeys = checkRequiredApiKeys(updatedGallery);
      if (missingKeys.length > 0) {
        message.warning(
          `Some tools require API keys: ${missingKeys.join(", ")}`
        );
      }

      // Save to backend
      const response = await fetch(`/api/galleries/${gallery.id}`, {
        method: "PATCH",
        body: JSON.stringify(updatedGallery),
        headers: { "Content-Type": "application/json" }
      });

      if (response.ok) {
        setGallery(updatedGallery);
        message.success("Gallery saved successfully");
      } else {
        throw new Error("Failed to save gallery");
      }
    } catch (error) {
      console.error("Save error:", error);
      message.error("Failed to save gallery");
    }
  };

  return (
    <GalleryDetail
      gallery={gallery}
      onSave={handleSave}
      onDirtyStateChange={(isDirty) => console.log("Dirty:", isDirty)}
    />
  );
}

function validateGallery(gallery: Gallery): boolean {
  // Validate required fields
  if (!gallery.config.name || !gallery.config.metadata.version) {
    return false;
  }

  // Validate each component type
  for (const category of Object.keys(gallery.config.components)) {
    const components = gallery.config.components[category];
    for (const component of components) {
      if (!component.provider || !component.component_type) {
        return false;
      }
    }
  }

  return true;
}

Loading Remote Gallery

function RemoteGalleryPage({ galleryId }: { galleryId: number }) {
  const [gallery, setGallery] = useState<Gallery | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    loadGallery();
  }, [galleryId]);

  const loadGallery = async () => {
    try {
      setLoading(true);
      const response = await fetch(`/api/galleries/${galleryId}`);
      if (!response.ok) throw new Error("Failed to load gallery");

      const data = await response.json();
      setGallery(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  const handleSave = async (updates: Partial<Gallery>) => {
    if (!gallery) return;

    const updatedGallery = { ...gallery, ...updates };
    const response = await fetch(`/api/galleries/${galleryId}`, {
      method: "PATCH",
      body: JSON.stringify(updatedGallery),
      headers: { "Content-Type": "application/json" }
    });

    if (response.ok) {
      setGallery(updatedGallery);
    }
  };

  if (loading) return <div>Loading gallery...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!gallery) return <div>Gallery not found</div>;

  return (
    <GalleryDetail
      gallery={gallery}
      onSave={handleSave}
      onDirtyStateChange={(isDirty) => console.log("Dirty:", isDirty)}
    />
  );
}

Component Features

Component Card Actions

Each component card in the grid supports three actions:

Edit: Opens the component in a side drawer with full ComponentEditor interface Duplicate: Creates a copy with auto-incremented label (e.g., "Agent_1", "Agent_2") Delete: Removes the component (disabled if it's the only component of that type)

JSON Editor Features

  • Full Monaco Editor integration with JSON syntax highlighting
  • Real-time dirty state tracking
  • Validation on save with error messaging
  • Cancel operation to revert changes
  • Preserves working copy separate from main gallery during editing

Gallery Metadata Editing

  • Inline editing of gallery name and description
  • Visual edit/save/cancel buttons
  • Updates reflected immediately in banner
  • Integrated with main save flow

Component Addition

  • Template-based component creation via AddComponentDropdown
  • Automatic navigation to new component for immediate editing
  • Smart label generation for new components
  • Integration with component template system

Related Pages

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment