From 2d8d77bbee987cb7258a7c341357d31c5c347983 Mon Sep 17 00:00:00 2001 From: Smita Ambiger Date: Mon, 9 Mar 2026 21:58:04 +0530 Subject: [PATCH 1/4] Add create_burr_ui_app factory and mount_burr_ui helper to embed Burr UI into existing FastAPI apps --- burr/tracking/server/run.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/burr/tracking/server/run.py b/burr/tracking/server/run.py index 0e5ce62b0..c994992da 100644 --- a/burr/tracking/server/run.py +++ b/burr/tracking/server/run.py @@ -141,7 +141,17 @@ async def lifespan(app: FastAPI): await backend.lifespan(app).__anext__() -app = FastAPI(lifespan=lifespan) +def create_burr_ui_app() -> FastAPI: + return FastAPI(lifespan=lifespan) + +app = create_burr_ui_app() + +def mount_burr_ui(parent_app: FastAPI, path: str = "/burr") -> None: + """ + Mount the Burr UI inside another FastAPI app. + """ + ui_app = create_burr_ui_app() + parent_app.mount(path, ui_app, name="burr-ui") @app.get("/ready") From a3301b2575f910dbe61b2a4bbedd07e092749103 Mon Sep 17 00:00:00 2001 From: Smita Ambiger Date: Fri, 13 Mar 2026 18:32:42 +0530 Subject: [PATCH 2/4] Add FastAPI mount example and documentation for Burr UI embedding --- docs/concepts/tracking.rst | 22 ++++++++++++++++++++++ examples/fastapi_mount_example.py | 24 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 examples/fastapi_mount_example.py diff --git a/docs/concepts/tracking.rst b/docs/concepts/tracking.rst index d22b9f89b..06322358a 100644 --- a/docs/concepts/tracking.rst +++ b/docs/concepts/tracking.rst @@ -131,3 +131,25 @@ This will print the URL to access the Burr UI web app. from google.colab import output output.serve_kernel_port_as_window(7241) # this will open a new window output.serve_kernel_port_as_iframe(7241) # this will inline in an iframe + +--------------------------------------------- +Mount Burr UI inside an existing FastAPI app +--------------------------------------------- + +You can embed the Burr UI inside an existing FastAPI application using the +``mount_burr_ui`` helper. + +Example: + +.. code-block:: python + + from fastapi import FastAPI + from burr.tracking.server.run import mount_burr_ui + + app = FastAPI() + + # Mount Burr UI under /burr + mount_burr_ui(app, path="/burr") + +This allows you to run the Burr tracking UI alongside your own FastAPI +application in the same server process. \ No newline at end of file diff --git a/examples/fastapi_mount_example.py b/examples/fastapi_mount_example.py new file mode 100644 index 000000000..2c7970c8f --- /dev/null +++ b/examples/fastapi_mount_example.py @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0. + +from fastapi import FastAPI +import uvicorn + +from burr.tracking.server.run import mount_burr_ui + +app = FastAPI() + +# Mount Burr UI +mount_burr_ui(app, path="/burr") + + +@app.get("/") +def root(): + return {"message": "Main FastAPI app with Burr UI mounted"} + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) From 655e9239468d01ba463b812cd2357afce8f5d069 Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sat, 21 Mar 2026 12:50:21 -0700 Subject: [PATCH 3/4] feat: enable Burr UI mounting as sub-app with dynamic base path support Refactor run.py to use a factory pattern (create_burr_ui_app) so all routes are registered on the sub-app instance, not at module level. Add mount_burr_ui() helper for embedding the Burr UI into an existing FastAPI application. When mounted at a sub-path (e.g. /burr), the server rewrites CRA's hardcoded absolute paths in index.html and injects window.__BURR_BASE_PATH__ so the React app can prefix all API calls and client-side routes at runtime. React-side changes: - OpenAPI.ts reads __BURR_BASE_PATH__ for API client BASE - App.tsx uses it as Router basename - appcontainer.tsx prefixes logo image paths - StreamingChatbot.tsx prefixes direct fetch calls --- burr/tracking/server/run.py | 423 ++++++++++-------- docs/concepts/tracking.rst | 2 +- examples/fastapi_mount_example.py | 25 +- telemetry/ui/package-lock.json | 153 +++++++ telemetry/ui/src/App.tsx | 2 +- telemetry/ui/src/api/core/OpenAPI.ts | 8 +- .../ui/src/components/nav/appcontainer.tsx | 4 +- .../ui/src/examples/StreamingChatbot.tsx | 3 +- 8 files changed, 424 insertions(+), 196 deletions(-) diff --git a/burr/tracking/server/run.py b/burr/tracking/server/run.py index c994992da..2e9759659 100644 --- a/burr/tracking/server/run.py +++ b/burr/tracking/server/run.py @@ -42,7 +42,6 @@ from fastapi import FastAPI, HTTPException, Request from fastapi.staticfiles import StaticFiles from fastapi_utils.tasks import repeat_every - from starlette.templating import Jinja2Templates from burr.tracking.server import schema from burr.tracking.server.schema import ( # AnnotationUpdate, @@ -141,30 +140,8 @@ async def lifespan(app: FastAPI): await backend.lifespan(app).__anext__() -def create_burr_ui_app() -> FastAPI: - return FastAPI(lifespan=lifespan) - -app = create_burr_ui_app() - -def mount_burr_ui(parent_app: FastAPI, path: str = "/burr") -> None: - """ - Mount the Burr UI inside another FastAPI app. - """ - ui_app = create_burr_ui_app() - parent_app.mount(path, ui_app, name="burr-ui") - - -@app.get("/ready") -def is_ready(): - if not initialized: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Backend is not ready yet." - ) - return {"ready": True} - - -@app.get("/api/v0/metadata/app_spec", response_model=BackendSpec) -def get_app_spec(): +def _get_app_spec() -> BackendSpec: + """Computes the backend spec from the current backend configuration.""" is_indexing_backend = isinstance(backend, IndexingBackendMixin) is_snapshotting_backend = isinstance(backend, SnapshottingBackendMixin) is_annotations_backend = isinstance(backend, AnnotationsBackendMixin) @@ -177,7 +154,7 @@ def get_app_spec(): ) -app_spec = get_app_spec() +app_spec = _get_app_spec() logger = logging.getLogger(__name__) @@ -200,178 +177,258 @@ def get_app_spec(): )(save_snapshot) -@app.get("/api/v0/projects", response_model=Sequence[schema.Project]) -async def get_projects(request: Request) -> Sequence[schema.Project]: - """Gets all projects visible by the user. - - :param request: FastAPI request - :return: a list of projects visible by the user - """ - return await backend.list_projects(request) - - -@app.get("/api/v0/{project_id}/{partition_key}/apps", response_model=ApplicationPage) -async def get_apps( - request: Request, - project_id: str, - partition_key: str, - limit: int = 100, - offset: int = 0, -) -> ApplicationPage: - """Gets all apps visible by the user - - :param request: FastAPI request - :param project_id: project name - :return: a list of projects visible by the user - """ - if partition_key == SENTINEL_PARTITION_KEY: - partition_key = None - applications, total_count = await backend.list_apps( - request, project_id, partition_key=partition_key, limit=limit, offset=offset - ) - return ApplicationPage( - applications=list(applications), - total=total_count, - has_another_page=total_count > offset + limit, - ) +def create_burr_ui_app(serve_static: bool = SERVE_STATIC) -> FastAPI: + """Create a fully-configured Burr UI FastAPI application. + This factory creates a new FastAPI instance with all Burr UI routes, + demo routers, and (optionally) static file serving configured. -@app.get("/api/v0/{project_id}/{app_id}/{partition_key}/apps") -async def get_application_logs( - request: Request, project_id: str, app_id: str, partition_key: str -) -> ApplicationLogs: - """Lists steps for a given App. - TODO: add streaming capabilities for bi-directional communication - TODO: add pagination for quicker loading - - :param request: FastAPI - :param project_id: ID of the project - :param app_id: ID of the assIndociated application - :return: A list of steps with all associated step data + :param serve_static: Whether to serve the React UI static files. Defaults to + the BURR_SERVE_STATIC environment variable (true by default). + :return: A fully-configured FastAPI application. """ - if partition_key == SENTINEL_PARTITION_KEY: - partition_key = None - return await backend.get_application_logs( - request, project_id=project_id, app_id=app_id, partition_key=partition_key - ) + ui_app = FastAPI(lifespan=lifespan) + + @ui_app.get("/ready") + def is_ready(): + if not initialized: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Backend is not ready yet.", + ) + return {"ready": True} + + @ui_app.get("/api/v0/metadata/app_spec", response_model=BackendSpec) + def get_app_spec(): + return _get_app_spec() + + @ui_app.get("/api/v0/projects", response_model=Sequence[schema.Project]) + async def get_projects(request: Request) -> Sequence[schema.Project]: + """Gets all projects visible by the user. + + :param request: FastAPI request + :return: a list of projects visible by the user + """ + return await backend.list_projects(request) + + @ui_app.get("/api/v0/{project_id}/{partition_key}/apps", response_model=ApplicationPage) + async def get_apps( + request: Request, + project_id: str, + partition_key: str, + limit: int = 100, + offset: int = 0, + ) -> ApplicationPage: + """Gets all apps visible by the user + + :param request: FastAPI request + :param project_id: project name + :return: a list of projects visible by the user + """ + if partition_key == SENTINEL_PARTITION_KEY: + partition_key = None + applications, total_count = await backend.list_apps( + request, project_id, partition_key=partition_key, limit=limit, offset=offset + ) + return ApplicationPage( + applications=list(applications), + total=total_count, + has_another_page=total_count > offset + limit, + ) + @ui_app.get("/api/v0/{project_id}/{app_id}/{partition_key}/apps") + async def get_application_logs( + request: Request, project_id: str, app_id: str, partition_key: str + ) -> ApplicationLogs: + """Lists steps for a given App. + TODO: add streaming capabilities for bi-directional communication + TODO: add pagination for quicker loading + + :param request: FastAPI + :param project_id: ID of the project + :param app_id: ID of the assIndociated application + :return: A list of steps with all associated step data + """ + if partition_key == SENTINEL_PARTITION_KEY: + partition_key = None + return await backend.get_application_logs( + request, project_id=project_id, app_id=app_id, partition_key=partition_key + ) -@app.post( - "/api/v0/{project_id}/{app_id}/{partition_key}/{sequence_id}/annotations", - response_model=AnnotationOut, -) -async def create_annotation( - request: Request, - project_id: str, - app_id: str, - partition_key: str, - sequence_id: int, - annotation: AnnotationCreate, -): - if partition_key == SENTINEL_PARTITION_KEY: - partition_key = None - spec = get_app_spec() - if not spec.supports_annotations: - return [] # empty default -- the case that we don't support annotations - return await backend.create_annotation( - annotation, project_id, partition_key, app_id, sequence_id + @ui_app.post( + "/api/v0/{project_id}/{app_id}/{partition_key}/{sequence_id}/annotations", + response_model=AnnotationOut, ) + async def create_annotation( + request: Request, + project_id: str, + app_id: str, + partition_key: str, + sequence_id: int, + annotation: AnnotationCreate, + ): + if partition_key == SENTINEL_PARTITION_KEY: + partition_key = None + spec = _get_app_spec() + if not spec.supports_annotations: + return [] # empty default -- the case that we don't support annotations + return await backend.create_annotation( + annotation, project_id, partition_key, app_id, sequence_id + ) - -# -# # TODO -- take out these parameters cause we have the annotation ID -@app.put( - "/api/v0/{project_id}/{annotation_id}/update_annotations", - response_model=AnnotationOut, -) -async def update_annotation( - request: Request, - project_id: str, - annotation_id: int, - annotation: AnnotationUpdate, -): - return await backend.update_annotation( - annotation_id=annotation_id, annotation=annotation, project_id=project_id + # + # # TODO -- take out these parameters cause we have the annotation ID + @ui_app.put( + "/api/v0/{project_id}/{annotation_id}/update_annotations", + response_model=AnnotationOut, ) - - -@app.get("/api/v0/{project_id}/annotations", response_model=Sequence[AnnotationOut]) -async def get_annotations( - request: Request, - project_id: str, - app_id: Optional[str] = None, - partition_key: Optional[str] = None, - step_sequence_id: Optional[int] = None, -): - # Handle the sentinel value for partition_key - if partition_key == SENTINEL_PARTITION_KEY: - partition_key = None - backend_spec = get_app_spec() - - if not backend_spec.supports_annotations: - # makes it easier to wire through to the FE - return [] - - # Logic to retrieve the annotations - return await backend.get_annotations(project_id, partition_key, app_id, step_sequence_id) - - -@app.get("/api/v0/ready") -async def ready() -> bool: - return True - - -@app.get("/api/v0/indexing_jobs", response_model=Sequence[IndexingJob]) -async def get_indexing_jobs( - offset: int = 0, limit: int = 100, filter_empty: bool = True -) -> Sequence[IndexingJob]: - if not app_spec.indexing: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="This backend does not support indexing jobs.", + async def update_annotation( + request: Request, + project_id: str, + annotation_id: int, + annotation: AnnotationUpdate, + ): + return await backend.update_annotation( + annotation_id=annotation_id, annotation=annotation, project_id=project_id ) - return await backend.indexing_jobs(offset=offset, limit=limit, filter_empty=filter_empty) + @ui_app.get("/api/v0/{project_id}/annotations", response_model=Sequence[AnnotationOut]) + async def get_annotations( + request: Request, + project_id: str, + app_id: Optional[str] = None, + partition_key: Optional[str] = None, + step_sequence_id: Optional[int] = None, + ): + # Handle the sentinel value for partition_key + if partition_key == SENTINEL_PARTITION_KEY: + partition_key = None + backend_spec = _get_app_spec() + + if not backend_spec.supports_annotations: + # makes it easier to wire through to the FE + return [] + + # Logic to retrieve the annotations + return await backend.get_annotations(project_id, partition_key, app_id, step_sequence_id) + + @ui_app.get("/api/v0/ready") + async def ready() -> bool: + return True + + @ui_app.get("/api/v0/indexing_jobs", response_model=Sequence[IndexingJob]) + async def get_indexing_jobs( + offset: int = 0, limit: int = 100, filter_empty: bool = True + ) -> Sequence[IndexingJob]: + if not app_spec.indexing: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="This backend does not support indexing jobs.", + ) + return await backend.indexing_jobs(offset=offset, limit=limit, filter_empty=filter_empty) + + @ui_app.get("/api/v0/version") + async def version() -> dict: + """Returns the burr version""" + import pkg_resources -@app.get("/api/v0/version") -async def version() -> dict: - """Returns the burr version""" - import pkg_resources - - try: - version = pkg_resources.get_distribution("apache-burr").version - except pkg_resources.DistributionNotFound: try: - # Fallback for older installations or development - version = pkg_resources.get_distribution("burr").version + burr_version = pkg_resources.get_distribution("apache-burr").version except pkg_resources.DistributionNotFound: - version = "unknown" - return {"version": version} - - -# Examples -- todo -- put them behind `if` statements -app.include_router(chatbot.router, prefix="/api/v0/chatbot") -app.include_router(email_assistant.router, prefix="/api/v0/email_assistant") -app.include_router(streaming_chatbot.router, prefix="/api/v0/streaming_chatbot") -app.include_router(deep_researcher.router, prefix="/api/v0/deep_researcher") -app.include_router(counter.router, prefix="/api/v0/counter") - -if SERVE_STATIC: - BASE_ASSET_DIRECTORY = str(files("burr").joinpath("tracking/server/build")) + try: + # Fallback for older installations or development + burr_version = pkg_resources.get_distribution("burr").version + except pkg_resources.DistributionNotFound: + burr_version = "unknown" + return {"version": burr_version} + + # Examples -- todo -- put them behind `if` statements + ui_app.include_router(chatbot.router, prefix="/api/v0/chatbot") + ui_app.include_router(email_assistant.router, prefix="/api/v0/email_assistant") + ui_app.include_router(streaming_chatbot.router, prefix="/api/v0/streaming_chatbot") + ui_app.include_router(deep_researcher.router, prefix="/api/v0/deep_researcher") + ui_app.include_router(counter.router, prefix="/api/v0/counter") + + if serve_static: + base_asset_directory = str(files("burr").joinpath("tracking/server/build")) + static_directory = os.path.join(base_asset_directory, "static") + + ui_app.mount( + "/static", + StaticFiles(directory=static_directory), + "/static", + ) + # public assets in create react app don't get put under build/static, + # we need to route them over + ui_app.mount("/public", StaticFiles(directory=base_asset_directory, html=True), "/public") + + # Read index.html once at startup + with open(os.path.join(base_asset_directory, "index.html")) as f: + _index_html_template = f.read() + + @ui_app.get("/manifest.json") + async def manifest_json(): + """Serve manifest.json from the build directory.""" + from starlette.responses import FileResponse + + return FileResponse( + os.path.join(base_asset_directory, "manifest.json"), + media_type="application/manifest+json", + ) + + @ui_app.get("/{rest_of_path:path}") + async def react_app(req: Request, rest_of_path: str): + """Serves the React app, rewriting asset paths to respect the mount prefix. + + When mounted as a sub-app (e.g. under /burr), the CRA build's hardcoded + absolute paths (/static/js/..., /api/v0/...) need to be prefixed with the + mount path so the browser fetches them from the correct location. + + This rewrites both the HTML (href/src attributes) and injects a script that + sets the OpenAPI client's BASE to the mount prefix, so all API calls are + also correctly routed. + """ + from starlette.responses import HTMLResponse + + root_path = req.scope.get("root_path", "") + if root_path: + # Rewrite CRA's absolute paths to include the mount prefix + html = _index_html_template.replace('href="/', f'href="{root_path}/') + html = html.replace('src="/', f'src="{root_path}/') + # Inject a script before that patches the OpenAPI BASE config + # so all runtime API calls (fetch) go to the correct mount path. + # Also patches image/asset references that use /public/. + patch_script = f"" + html = html.replace("", f"{patch_script}") + else: + html = _index_html_template + return HTMLResponse(html) + + return ui_app + + +def mount_burr_ui( + parent_app: FastAPI, + path: str = "/burr", + name: str = "burr-ui", + serve_static: bool = SERVE_STATIC, +) -> FastAPI: + """Mount the Burr UI inside another FastAPI app. + + :param parent_app: The parent FastAPI application to mount onto. + :param path: URL path prefix for the Burr UI. Defaults to "/burr". + :param name: Name for the mounted sub-application. Defaults to "burr-ui". + :param serve_static: Whether to serve the React UI static files. Defaults to + the BURR_SERVE_STATIC environment variable (true by default). + :return: The mounted Burr UI FastAPI app instance. + """ + ui_app = create_burr_ui_app(serve_static=serve_static) + parent_app.mount(path, ui_app, name=name) + return ui_app - templates = Jinja2Templates(directory=BASE_ASSET_DIRECTORY) - app.mount( - "/static", StaticFiles(directory=os.path.join(BASE_ASSET_DIRECTORY, "static")), "/static" - ) - # public assets in create react app don't get put under build/static, we need to route them over - app.mount("/public", StaticFiles(directory=BASE_ASSET_DIRECTORY, html=True), "/public") - @app.get("/{rest_of_path:path}") - async def react_app(req: Request, rest_of_path: str): - """Quick trick to server the react app - Thanks to https://github.com/hop-along-polly/fastapi-webapp-react for the example/demo - """ - return templates.TemplateResponse("index.html", {"request": req}) +# Module-level app for backwards compatibility (used by uvicorn, CLI, etc.) +app = create_burr_ui_app() if __name__ == "__main__": diff --git a/docs/concepts/tracking.rst b/docs/concepts/tracking.rst index 06322358a..1e69b4525 100644 --- a/docs/concepts/tracking.rst +++ b/docs/concepts/tracking.rst @@ -152,4 +152,4 @@ Example: mount_burr_ui(app, path="/burr") This allows you to run the Burr tracking UI alongside your own FastAPI -application in the same server process. \ No newline at end of file +application in the same server process. diff --git a/examples/fastapi_mount_example.py b/examples/fastapi_mount_example.py index 2c7970c8f..aa1e9922c 100644 --- a/examples/fastapi_mount_example.py +++ b/examples/fastapi_mount_example.py @@ -1,24 +1,35 @@ # Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file +# or more contributor license agreements. See the NOTICE file # distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0. +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. -from fastapi import FastAPI import uvicorn +from fastapi import FastAPI from burr.tracking.server.run import mount_burr_ui app = FastAPI() -# Mount Burr UI +# Mount Burr UI under /burr mount_burr_ui(app, path="/burr") @app.get("/") def root(): - return {"message": "Main FastAPI app with Burr UI mounted"} + return {"message": "Main FastAPI app with Burr UI mounted at /burr"} if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8000) + uvicorn.run(app, host="0.0.0.0", port=8003) diff --git a/telemetry/ui/package-lock.json b/telemetry/ui/package-lock.json index 21461ed4e..95f8310a7 100644 --- a/telemetry/ui/package-lock.json +++ b/telemetry/ui/package-lock.json @@ -5566,6 +5566,159 @@ "integrity": "sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg==", "peer": true }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.14.tgz", + "integrity": "sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.14.tgz", + "integrity": "sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.14.tgz", + "integrity": "sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.14.tgz", + "integrity": "sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.14.tgz", + "integrity": "sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.14.tgz", + "integrity": "sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.14.tgz", + "integrity": "sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.14.tgz", + "integrity": "sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.14.tgz", + "integrity": "sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", diff --git a/telemetry/ui/src/App.tsx b/telemetry/ui/src/App.tsx index 4b80c5bd8..1e8316e5e 100644 --- a/telemetry/ui/src/App.tsx +++ b/telemetry/ui/src/App.tsx @@ -50,7 +50,7 @@ import { DeepResearcherWithTelemetry } from './examples/DeepResearcher'; const App = () => { return ( - + } /> diff --git a/telemetry/ui/src/api/core/OpenAPI.ts b/telemetry/ui/src/api/core/OpenAPI.ts index 48cc36aa5..ec80e616d 100644 --- a/telemetry/ui/src/api/core/OpenAPI.ts +++ b/telemetry/ui/src/api/core/OpenAPI.ts @@ -38,8 +38,14 @@ export type OpenAPIConfig = { ENCODE_PATH?: ((path: string) => string) | undefined; }; +// When the Burr UI is mounted as a sub-app (e.g. under /burr), the server +// injects window.__BURR_BASE_PATH__ so API calls are correctly prefixed. +const basePath = typeof window !== 'undefined' + ? (window as any).__BURR_BASE_PATH__ || '' + : ''; + export const OpenAPI: OpenAPIConfig = { - BASE: '', + BASE: basePath, VERSION: '0.1.0', WITH_CREDENTIALS: false, CREDENTIALS: 'include', diff --git a/telemetry/ui/src/components/nav/appcontainer.tsx b/telemetry/ui/src/components/nav/appcontainer.tsx index 86e9614d1..b0d143013 100644 --- a/telemetry/ui/src/components/nav/appcontainer.tsx +++ b/telemetry/ui/src/components/nav/appcontainer.tsx @@ -231,7 +231,7 @@ export const AppContainer = (props: { children: React.ReactNode }) => { {/* Sidebar component, swap this element with another sidebar if you like */}
- Burr + Burr