Implementation:Microsoft Autogen Studio Gallery Detail
| 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>
);
}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
- Studio Component Editor - Detailed component editing interface
- Studio Default Gallery - Default gallery JSON structure
- Studio Datamodel Types - Type definitions for gallery and components
- Studio Component Templates - Template system for creating components
- Studio Monaco Editor - JSON editor implementation
- Studio Add Component Dropdown - Component creation UI