Skip to content

Commit 822efe0

Browse files
hoonjicopybara-github
authored andcommitted
feat: Adds adk web options for custom logo
Allows users to configure a custom text and logo for their ADK Web app using `--logo-text` and `--logo-image-url` flags. PiperOrigin-RevId: 814016542
1 parent 55bc985 commit 822efe0

3 files changed

Lines changed: 100 additions & 0 deletions

File tree

src/google/adk/cli/adk_web_server.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import asyncio
1818
from contextlib import asynccontextmanager
1919
import importlib
20+
import json
2021
import logging
2122
import os
2223
import time
@@ -395,6 +396,9 @@ class AdkWebServer:
395396
managing evaluation set results.
396397
agents_dir: Root directory containing subdirs for agents with those
397398
containing resources (e.g. .env files, eval sets, etc.) for the agents.
399+
extra_plugins: A list of fully qualified names of extra plugins to load.
400+
logo_text: Text to display in the logo of the UI.
401+
logo_image_url: URL of an image to display as logo of the UI.
398402
runners_to_clean: Set of runner names marked for cleanup.
399403
current_app_name_ref: A shared reference to the latest ran app name.
400404
runner_dict: A dict of instantiated runners for each app.
@@ -412,6 +416,8 @@ def __init__(
412416
eval_set_results_manager: EvalSetResultsManager,
413417
agents_dir: str,
414418
extra_plugins: Optional[list[str]] = None,
419+
logo_text: Optional[str] = None,
420+
logo_image_url: Optional[str] = None,
415421
):
416422
self.agent_loader = agent_loader
417423
self.session_service = session_service
@@ -422,6 +428,8 @@ def __init__(
422428
self.eval_set_results_manager = eval_set_results_manager
423429
self.agents_dir = agents_dir
424430
self.extra_plugins = extra_plugins or []
431+
self.logo_text = logo_text
432+
self.logo_image_url = logo_image_url
425433
# Internal propeties we want to allow being modified from callbacks.
426434
self.runners_to_clean: set[str] = set()
427435
self.current_app_name_ref: SharedValue[str] = SharedValue(value="")
@@ -506,6 +514,52 @@ def _import_plugin_object(self, qualified_name: str) -> Any:
506514
module = importlib.import_module(module_name)
507515
return getattr(module, obj_name)
508516

517+
def _setup_runtime_config(self, web_assets_dir: str):
518+
"""Sets up the runtime config for the web server."""
519+
# Read existing runtime config file.
520+
runtime_config_path = os.path.join(
521+
web_assets_dir, "assets", "config", "runtime-config.json"
522+
)
523+
runtime_config = {}
524+
try:
525+
with open(runtime_config_path, "r") as f:
526+
runtime_config = json.load(f)
527+
except FileNotFoundError:
528+
logger.info(
529+
"File not found: %s. A new runtime config file will be created.",
530+
runtime_config_path,
531+
)
532+
except json.JSONDecodeError:
533+
logger.warning(
534+
"Failed to decode JSON from %s. The file content will be"
535+
" overwritten.",
536+
runtime_config_path,
537+
)
538+
539+
# Set custom logo config.
540+
if self.logo_text or self.logo_image_url:
541+
if not self.logo_text or not self.logo_image_url:
542+
raise ValueError(
543+
"Both --logo-text and --logo-image-url must be defined when using"
544+
" logo config."
545+
)
546+
runtime_config["logo"] = {
547+
"text": self.logo_text,
548+
"imageUrl": self.logo_image_url,
549+
}
550+
elif "logo" in runtime_config:
551+
del runtime_config["logo"]
552+
553+
# Write the runtime config file.
554+
try:
555+
os.makedirs(os.path.dirname(runtime_config_path), exist_ok=True)
556+
with open(runtime_config_path, "w") as f:
557+
json.dump(runtime_config, f, indent=2)
558+
except IOError as e:
559+
logger.error(
560+
"Failed to write runtime config file %s: %s", runtime_config_path, e
561+
)
562+
509563
def get_fast_api_app(
510564
self,
511565
lifespan: Optional[Lifespan[FastAPI]] = None,
@@ -570,6 +624,8 @@ async def internal_lifespan(app: FastAPI):
570624
export_lib.SimpleSpanProcessor(memory_exporter),
571625
],
572626
)
627+
if web_assets_dir:
628+
self._setup_runtime_config(web_assets_dir)
573629

574630
# TODO - register_processors to be removed once --otel_to_cloud is no
575631
# longer experimental.
@@ -1413,6 +1469,13 @@ async def process_messages():
14131469
mimetypes.add_type("application/javascript", ".js", True)
14141470
mimetypes.add_type("text/javascript", ".js", True)
14151471

1472+
@app.get("/dev-ui/config")
1473+
async def get_ui_config():
1474+
return {
1475+
"logo_text": self.logo_text,
1476+
"logo_image_url": self.logo_image_url,
1477+
}
1478+
14161479
@app.get("/")
14171480
async def redirect_root_to_dev_ui():
14181481
return RedirectResponse("/dev-ui/")

src/google/adk/cli/cli_tools_click.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,34 @@ def cli_eval(
677677
pretty_print_eval_result(eval_result)
678678

679679

680+
def web_options():
681+
"""Decorator to add web UI options to click commands."""
682+
683+
def decorator(func):
684+
@click.option(
685+
"--logo-text",
686+
type=str,
687+
help="Optional. The text to display in the logo of the web UI.",
688+
default=None,
689+
)
690+
@click.option(
691+
"--logo-image-url",
692+
type=str,
693+
help=(
694+
"Optional. The URL of the image to display in the logo of the"
695+
" web UI."
696+
),
697+
default=None,
698+
)
699+
@functools.wraps(func)
700+
def wrapper(*args, **kwargs):
701+
return func(*args, **kwargs)
702+
703+
return wrapper
704+
705+
return decorator
706+
707+
680708
def adk_services_options():
681709
"""Decorator to add ADK services options to click commands."""
682710

@@ -872,6 +900,7 @@ def wrapper(ctx, *args, **kwargs):
872900

873901
@main.command("web")
874902
@fast_api_common_options()
903+
@web_options()
875904
@adk_services_options()
876905
@deprecated_adk_services_options()
877906
@click.argument(
@@ -899,6 +928,8 @@ def cli_web(
899928
a2a: bool = False,
900929
reload_agents: bool = False,
901930
extra_plugins: Optional[list[str]] = None,
931+
logo_text: Optional[str] = None,
932+
logo_image_url: Optional[str] = None,
902933
):
903934
"""Starts a FastAPI server with Web UI for agents.
904935
@@ -951,6 +982,8 @@ async def _lifespan(app: FastAPI):
951982
port=port,
952983
reload_agents=reload_agents,
953984
extra_plugins=extra_plugins,
985+
logo_text=logo_text,
986+
logo_image_url=logo_image_url,
954987
)
955988
config = uvicorn.Config(
956989
app,

src/google/adk/cli/fast_api.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ def get_fast_api_app(
7171
reload_agents: bool = False,
7272
lifespan: Optional[Lifespan[FastAPI]] = None,
7373
extra_plugins: Optional[list[str]] = None,
74+
logo_text: Optional[str] = None,
75+
logo_image_url: Optional[str] = None,
7476
) -> FastAPI:
7577
# Set up eval managers.
7678
if eval_storage_uri:
@@ -189,6 +191,8 @@ def _parse_agent_engine_resource_name(agent_engine_id_or_resource_name):
189191
eval_set_results_manager=eval_set_results_manager,
190192
agents_dir=agents_dir,
191193
extra_plugins=extra_plugins,
194+
logo_text=logo_text,
195+
logo_image_url=logo_image_url,
192196
)
193197

194198
# Callbacks & other optional args for when constructing the FastAPI instance

0 commit comments

Comments
 (0)