Skip to content

Commit 9a0661d

Browse files
committed
Make AutoGen Studio Gallery feature use a database (rather than localstorage) #5619
1 parent 2d68a27 commit 9a0661d

File tree

22 files changed

+790
-673
lines changed

22 files changed

+790
-673
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from .db import Message, Run, RunStatus, Session, Settings, Team
1+
from .db import Run, RunStatus, Session, Team, Message, Settings, Gallery
22
from .types import (
3-
EnvironmentVariable,
4-
Gallery,
3+
GalleryConfig,
54
GalleryComponents,
65
GalleryMetadata,
76
LLMCallEventMessage,
@@ -11,6 +10,8 @@
1110
SettingsConfig,
1211
SocketMessage,
1312
TeamResult,
13+
EnvironmentVariable,
14+
1415
)
1516

1617
__all__ = [
@@ -19,15 +20,18 @@
1920
"RunStatus",
2021
"Session",
2122
"Team",
23+
"Message",
2224
"MessageConfig",
2325
"MessageMeta",
2426
"TeamResult",
2527
"Response",
2628
"SocketMessage",
2729
"LLMCallEventMessage",
28-
"Gallery",
30+
"GalleryConfig",
2931
"GalleryComponents",
3032
"GalleryMetadata",
3133
"SettingsConfig",
32-
"Settings" "EnvironmentVariable",
34+
"Settings",
35+
"EnvironmentVariable",
36+
"Gallery",
3337
]

python/packages/autogen-studio/autogenstudio/datamodel/db.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from sqlalchemy import ForeignKey, Integer
1111
from sqlmodel import JSON, Column, DateTime, Field, SQLModel, func
1212

13-
from .types import Gallery, MessageConfig, MessageMeta, SettingsConfig, TeamResult
13+
from .types import GalleryConfig, MessageConfig, MessageMeta, SettingsConfig, TeamResult
1414

1515

1616
class Team(SQLModel, table=True):
@@ -47,8 +47,8 @@ class Message(SQLModel, table=True):
4747
default=None, sa_column=Column(Integer, ForeignKey("session.id", ondelete="CASCADE"))
4848
)
4949
run_id: Optional[UUID] = Field(default=None, foreign_key="run.id")
50-
message_meta: Optional[Union[MessageMeta, dict]] = Field(default={}, sa_column=Column(JSON))
5150

51+
message_meta: Optional[Union[MessageMeta, dict]] = Field(default={}, sa_column=Column(JSON))
5252

5353
class Session(SQLModel, table=True):
5454
__table_args__ = {"sqlite_autoincrement": True}
@@ -119,7 +119,16 @@ class Gallery(SQLModel, table=True):
119119
) # pylint: disable=not-callable
120120
user_id: Optional[str] = None
121121
version: Optional[str] = "0.0.1"
122-
config: Union[Gallery, dict] = Field(default_factory=Gallery, sa_column=Column(JSON))
122+
config: Union[GalleryConfig, dict] = Field(default_factory=GalleryConfig, sa_column=Column(JSON))
123+
124+
model_config = ConfigDict(
125+
json_encoders={
126+
datetime: lambda v: v.isoformat(),
127+
UUID: str
128+
}
129+
)
130+
131+
123132

124133

125134
class Settings(SQLModel, table=True):

python/packages/autogen-studio/autogenstudio/datamodel/types.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from dataclasses import Field
1+
# from dataclasses import Field
22
from datetime import datetime
33
from typing import Any, Dict, List, Literal, Optional
44

55
from autogen_agentchat.base import TaskResult
66
from autogen_agentchat.messages import BaseChatMessage
77
from autogen_core import ComponentModel
8-
from pydantic import BaseModel
8+
from pydantic import BaseModel, ConfigDict, Field
99

1010

1111
class MessageConfig(BaseModel):
@@ -37,8 +37,8 @@ class MessageMeta(BaseModel):
3737

3838
class GalleryMetadata(BaseModel):
3939
author: str
40-
created_at: datetime
41-
updated_at: datetime
40+
# created_at: datetime = Field(default_factory=datetime.now)
41+
# updated_at: datetime = Field(default_factory=datetime.now)
4242
version: str
4343
description: Optional[str] = None
4444
tags: Optional[List[str]] = None
@@ -47,6 +47,12 @@ class GalleryMetadata(BaseModel):
4747
category: Optional[str] = None
4848
last_synced: Optional[datetime] = None
4949

50+
model_config = ConfigDict(
51+
json_encoders={
52+
datetime: lambda v: v.isoformat(),
53+
}
54+
)
55+
5056

5157
class GalleryComponents(BaseModel):
5258
agents: List[ComponentModel]
@@ -56,13 +62,19 @@ class GalleryComponents(BaseModel):
5662
teams: List[ComponentModel]
5763

5864

59-
class Gallery(BaseModel):
65+
class GalleryConfig(BaseModel):
6066
id: str
6167
name: str
6268
url: Optional[str] = None
6369
metadata: GalleryMetadata
6470
components: GalleryComponents
6571

72+
model_config = ConfigDict(
73+
json_encoders={
74+
datetime: lambda v: v.isoformat(),
75+
}
76+
)
77+
6678

6779
class EnvironmentVariable(BaseModel):
6880
name: str

python/packages/autogen-studio/autogenstudio/gallery/builder.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ def __init__(self, id: str, name: str, url: Optional[str] = None):
3131

3232
# Default metadata
3333
self.metadata = GalleryMetadata(
34-
author="AutoGen Team",
35-
created_at=datetime.now(),
36-
updated_at=datetime.now(),
34+
author="AutoGen Team",
3735
version="1.0.0",
3836
description="",
3937
tags=[],
@@ -113,7 +111,7 @@ def add_termination(
113111
def build(self) -> GalleryConfig:
114112
"""Build and return the complete gallery."""
115113
# Update timestamps
116-
self.metadata.updated_at = datetime.now()
114+
# self.metadata.updated_at = datetime.now()
117115

118116
return GalleryConfig(
119117
id=self.id,
@@ -292,12 +290,7 @@ def create_default_gallery() -> GalleryConfig:
292290
label="Image Generation Tool",
293291
description="A tool that generates images based on a text description using OpenAI's DALL-E model. Note: Requires OpenAI API key to function.",
294292
)
295-
296-
builder.add_tool(
297-
tools.generate_pdf_tool.dump_component(),
298-
label="PDF Generation Tool",
299-
description="A tool that generates a PDF file from a list of images.Requires the PyFPDF and pillow library to function.",
300-
)
293+
301294

302295
builder.add_tool(
303296
tools.fetch_webpage_tool.dump_component(),
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from .bing_search import bing_search_tool
22
from .calculator import calculator_tool
33
from .fetch_webpage import fetch_webpage_tool
4-
from .generate_image import generate_image_tool
5-
from .generate_pdf import generate_pdf_tool
4+
from .generate_image import generate_image_tool
65
from .google_search import google_search_tool
76

87
__all__ = [
98
"bing_search_tool",
109
"calculator_tool",
1110
"google_search_tool",
12-
"generate_image_tool",
13-
"generate_pdf_tool",
11+
"generate_image_tool",
1412
"fetch_webpage_tool",
1513
]

python/packages/autogen-studio/autogenstudio/web/app.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .config import settings
1414
from .deps import cleanup_managers, init_managers
1515
from .initialization import AppInitializer
16-
from .routes import runs, sessions, settingsroute, teams, validation, ws
16+
from .routes import runs, sessions, settingsroute, teams, validation, ws, gallery
1717

1818
# Initialize application
1919
app_file_path = os.path.dirname(os.path.abspath(__file__))
@@ -121,6 +121,13 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
121121
responses={404: {"description": "Not found"}},
122122
)
123123

124+
api.include_router(
125+
gallery.router,
126+
prefix="/gallery",
127+
tags=["gallery"],
128+
responses={404: {"description": "Not found"}},
129+
)
130+
124131
# Version endpoint
125132

126133

python/packages/autogen-studio/autogenstudio/web/initialization.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def _load_environment(self) -> None:
7474
"""Load environment variables from .env file if it exists"""
7575
env_file = self.app_root / ".env"
7676
if env_file.exists():
77-
logger.info(f"Loading environment variables from {env_file}")
77+
# logger.info(f"Loading environment variables from {env_file}")
7878
load_dotenv(str(env_file))
7979

8080
# Properties for accessing paths

python/packages/autogen-studio/autogenstudio/web/routes/gallery.py

+51-34
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,78 @@
22
from fastapi import APIRouter, Depends, HTTPException
33

44
from ...database import DatabaseManager
5-
from ...datamodel import Gallery, GalleryConfig, Response, Run, Session
5+
from ...datamodel import Gallery, Response
66
from ..deps import get_db
7+
from ...gallery.builder import create_default_gallery
78

89
router = APIRouter()
910

11+
1012

11-
@router.post("/")
12-
async def create_gallery_entry(
13-
gallery_data: GalleryConfig, user_id: str, db: DatabaseManager = Depends(get_db)
13+
@router.put("/{gallery_id}")
14+
async def update_gallery_entry(
15+
gallery_id: int,
16+
gallery_data: Gallery,
17+
user_id: str,
18+
db: DatabaseManager = Depends(get_db)
1419
) -> Response:
15-
# First validate that user owns all runs
16-
for run in gallery_data.runs:
17-
run_result = db.get(Run, filters={"id": run.id})
18-
if not run_result.status or not run_result.data:
19-
raise HTTPException(status_code=404, detail=f"Run {run.id} not found")
20-
21-
# Get associated session to check ownership
22-
session_result = db.get(Session, filters={"id": run_result.data[0].session_id})
23-
if not session_result.status or not session_result.data or session_result.data[0].user_id != user_id:
24-
raise HTTPException(status_code=403, detail=f"Not authorized to add run {run.id} to gallery")
25-
26-
# Create gallery entry
27-
gallery = Gallery(user_id=user_id, config=gallery_data)
28-
result = db.upsert(gallery)
29-
return result
30-
31-
32-
@router.get("/{gallery_id}")
33-
async def get_gallery_entry(gallery_id: int, user_id: str, db: DatabaseManager = Depends(get_db)) -> Response:
20+
# Check ownership first
3421
result = db.get(Gallery, filters={"id": gallery_id})
3522
if not result.status or not result.data:
3623
raise HTTPException(status_code=404, detail="Gallery entry not found")
24+
25+
if result.data[0].user_id != user_id:
26+
raise HTTPException(
27+
status_code=403,
28+
detail="Not authorized to update this gallery entry"
29+
)
30+
31+
# Update if authorized
32+
gallery_data.id = gallery_id # Ensure ID matches
33+
gallery_data.user_id = user_id # Ensure user_id matches
34+
return db.upsert(gallery_data)
3735

38-
gallery = result.data[0]
39-
if gallery.config["visibility"] != "public" and gallery.user_id != user_id:
40-
raise HTTPException(status_code=403, detail="Not authorized to view this gallery entry")
4136

42-
return result
37+
38+
@router.post("/")
39+
async def create_gallery_entry(gallery_data: Gallery, db: DatabaseManager = Depends(get_db)) -> Response:
40+
response = db.upsert(gallery_data)
41+
if not response.status:
42+
raise HTTPException(status_code=400, detail=response.message)
43+
return response
4344

4445

4546
@router.get("/")
4647
async def list_gallery_entries(user_id: str, db: DatabaseManager = Depends(get_db)) -> Response:
4748
result = db.get(Gallery, filters={"user_id": user_id})
49+
if not result.data or len(result.data) == 0:
50+
# create a default gallery entry
51+
gallery_config = create_default_gallery()
52+
default_gallery = Gallery(user_id=user_id, config=gallery_config.model_dump())
53+
db.upsert(default_gallery)
54+
result = db.get(Gallery, filters={"user_id": user_id})
55+
4856
return result
4957

5058

51-
@router.delete("/{gallery_id}")
52-
async def delete_gallery_entry(gallery_id: int, user_id: str, db: DatabaseManager = Depends(get_db)) -> Response:
53-
# Check ownership first
54-
result = db.get(Gallery, filters={"id": gallery_id})
59+
@router.get("/{gallery_id}")
60+
async def get_gallery_entry(gallery_id: int, user_id: str, db: DatabaseManager = Depends(get_db)) -> Response:
61+
result = db.get(Gallery, filters={"id": gallery_id, "user_id": user_id})
5562
if not result.status or not result.data:
5663
raise HTTPException(status_code=404, detail="Gallery entry not found")
5764

58-
if result.data[0].user_id != user_id:
59-
raise HTTPException(status_code=403, detail="Not authorized to delete this gallery entry")
65+
return Response(status=result.status, data=result.data[0], message=result.message)
6066

67+
68+
69+
@router.delete("/{gallery_id}")
70+
async def delete_gallery_entry(gallery_id: int, user_id: str, db: DatabaseManager = Depends(get_db)) -> Response:
71+
# Check ownership first
72+
result = db.get(Gallery, filters={"id": gallery_id, "user_id": user_id})
73+
74+
if not result.status or not result.data:
75+
raise HTTPException(status_code=404, detail="Gallery entry not found")
76+
response = db.delete(Gallery, filters={"id": gallery_id})
6177
# Delete if authorized
62-
return db.delete(Gallery, filters={"id": gallery_id})
78+
return response
79+

python/packages/autogen-studio/frontend/src/components/types/datamodel.ts

+31
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,34 @@ export interface SettingsConfig {
337337
export interface Settings extends DBModel {
338338
config: SettingsConfig;
339339
}
340+
341+
export interface GalleryMetadata {
342+
author: string;
343+
created_at: string;
344+
updated_at: string;
345+
version: string;
346+
description?: string;
347+
tags?: string[];
348+
license?: string;
349+
homepage?: string;
350+
category?: string;
351+
lastSynced?: string;
352+
}
353+
354+
export interface GalleryConfig {
355+
id: string;
356+
name: string;
357+
url?: string;
358+
metadata: GalleryMetadata;
359+
components: {
360+
teams: Component<TeamConfig>[];
361+
agents: Component<AgentConfig>[];
362+
models: Component<ModelConfig>[];
363+
tools: Component<ToolConfig>[];
364+
terminations: Component<TerminationConfig>[];
365+
};
366+
}
367+
368+
export interface Gallery extends DBModel {
369+
config: GalleryConfig;
370+
}

0 commit comments

Comments
 (0)