| from __future__ import annotations |
|
|
| import json |
| from pathlib import Path |
| from typing import Any |
|
|
| import gradio as gr |
| import uvicorn |
|
|
| from api import api, job_manager, project_manager, settings |
| from renderer import RenderEngine |
| from renderer.core.models import AIReelsRequest |
| from renderer.scenes import Timeline |
| from renderer.studio import capability_catalog |
| from renderer.templates import apply_creative_style, apply_preset, list_creative_styles, list_platform_profiles, list_templates |
|
|
|
|
| def _mcp_description(summary: str, example: dict[str, Any]) -> str: |
| """Build a concise MCP description with a machine-readable JSON example.""" |
| payload = json.dumps(example, indent=2, ensure_ascii=True) |
| return f"{summary}\n\nExample JSON:\n```json\n{payload}\n```" |
|
|
|
|
| def create_dashboard() -> gr.Blocks: |
| with gr.Blocks(title="Ava2lon Studio AI") as dashboard: |
| gr.Markdown( |
| "# Ava2lon Studio AI\n" |
| "CPU-first CapCut-class video automation studio with REST API parity, async jobs, webhooks, and project JSON." |
| ) |
| with gr.Tab("Projects"): |
| project_name = gr.Textbox(label="Project name", value="Untitled Ava2lon Project") |
| project_metadata = gr.Textbox(label="Metadata JSON", lines=5, value=json.dumps({"platform": "tiktok"}, indent=2)) |
| project_create = gr.Button("Create Project", variant="primary") |
| project_list = gr.Button("Refresh Projects") |
| project_output = gr.JSON(label="Projects") |
| project_create.click(fn=create_project, inputs=[project_name, project_metadata], outputs=project_output, api_name="create_project", api_description=_mcp_description("Create a project from a name and JSON metadata object.", {"name": "Campaign 01", "metadata_json": '{"platform":"tiktok"}'})) |
| project_list.click(fn=list_projects, outputs=project_output, api_name="list_projects", api_description=_mcp_description("List saved studio projects.", {})) |
|
|
| with gr.Tab("Assets"): |
| gr.Markdown("Use `/upload` or `/assets/upload` for multipart assets, then attach them to a project with `/project/assets/add`.") |
| asset_project_id = gr.Textbox(label="Project ID") |
| asset_json = gr.Textbox(label="Asset JSON", lines=6, value=json.dumps({"path": "clip.mp4", "kind": "video"}, indent=2)) |
| asset_button = gr.Button("Attach Asset", variant="primary") |
| asset_output = gr.JSON(label="Project") |
| asset_button.click(fn=add_project_asset, inputs=[asset_project_id, asset_json], outputs=asset_output, api_name="add_project_asset", api_description=_mcp_description("Attach a media asset JSON object to an existing project.", {"project_id": "project_123", "asset_json": '{"path":"clip.mp4","kind":"video"}'})) |
|
|
| with gr.Tab("Timeline"): |
| timeline_project_id = gr.Textbox(label="Project ID") |
| timeline_track_type = gr.Dropdown(choices=["video", "audio", "text", "overlay", "sticker", "subtitle"], value="video", label="Track type") |
| timeline_item = gr.Textbox( |
| label="Timeline item JSON", |
| lines=8, |
| value=json.dumps({"media": "clip.mp4", "start": 0, "duration": 5, "caption": "Hook"}, indent=2), |
| ) |
| timeline_add_button = gr.Button("Add To Timeline", variant="primary") |
| timeline_output = gr.JSON(label="Project") |
| timeline_add_button.click(fn=add_timeline_item, inputs=[timeline_project_id, timeline_track_type, timeline_item], outputs=timeline_output, api_name="add_timeline_item", api_description=_mcp_description("Add a typed item to a project timeline.", {"project_id": "project_123", "track_type": "video", "item_json": '{"media":"clip.mp4","start":0,"duration":5,"caption":"Hook"}'})) |
|
|
| timeline_operation_json = gr.Textbox( |
| label="Operation JSON", |
| lines=8, |
| value=json.dumps({"operation": "split", "item_id": "clip_123", "params": {"offset": 2.5}}, indent=2), |
| ) |
| timeline_operation_button = gr.Button("Apply Operation") |
| timeline_operation_button.click(fn=apply_timeline_operation, inputs=[timeline_project_id, timeline_operation_json], outputs=timeline_output, api_name="apply_timeline_operation", api_description=_mcp_description("Apply a timeline operation such as split, trim, insert, or ripple delete.", {"project_id": "project_123", "operation_json": '{"operation":"split","item_id":"clip_123","params":{"offset":2.5}}'})) |
|
|
| with gr.Tab("Templates"): |
| template_button = gr.Button("Load Template Catalog") |
| template_output = gr.JSON(label="Templates") |
| template_button.click(fn=load_template_catalog, outputs=template_output, api_name="load_template_catalog", api_description=_mcp_description("Load available render and caption templates.", {})) |
|
|
| with gr.Tab("Effects"): |
| effect_button = gr.Button("Load Effect Catalog") |
| effect_output = gr.JSON(label="Effects") |
| effect_button.click(fn=load_effect_catalog, outputs=effect_output, api_name="load_effect_catalog", api_description=_mcp_description("Load available video effects.", {})) |
|
|
| with gr.Tab("Filters"): |
| filter_button = gr.Button("Load Filter Catalog") |
| filter_output = gr.JSON(label="Filters") |
| filter_button.click(fn=load_filter_catalog, outputs=filter_output, api_name="load_filter_catalog", api_description=_mcp_description("Load available video filters.", {})) |
|
|
| with gr.Tab("Captions"): |
| caption_text = gr.Textbox(label="Caption source text", lines=6) |
| caption_button = gr.Button("Submit Caption Job", variant="primary") |
| caption_output = gr.JSON(label="Caption Job") |
| caption_button.click(fn=submit_caption_job, inputs=caption_text, outputs=caption_output, api_name="submit_caption_job", api_description=_mcp_description("Submit a caption-generation job from source text.", {"text": "Launch faster with automated rendering."})) |
|
|
| with gr.Tab("Audio"): |
| audio_button = gr.Button("Load Audio Catalog") |
| audio_output = gr.JSON(label="Audio") |
| audio_button.click(fn=load_audio_catalog, outputs=audio_output, api_name="load_audio_catalog", api_description=_mcp_description("Load audio-processing and music-generation capabilities.", {})) |
|
|
| with gr.Tab("AI Tools"): |
| ai_tool = gr.Dropdown(choices=capability_catalog()["ai_editing"], value="auto_viral_score", label="AI tool") |
| ai_payload = gr.Textbox(label="AI payload JSON", lines=8, value=json.dumps({"platform": "tiktok", "text": "A strong opening hook"}, indent=2)) |
| ai_button = gr.Button("Submit AI Tool", variant="primary") |
| ai_output = gr.JSON(label="AI Job") |
| ai_button.click(fn=submit_ai_tool, inputs=[ai_tool, ai_payload], outputs=ai_output, api_name="submit_ai_tool", api_description=_mcp_description("Submit a configured AI editing tool job.", {"tool": "auto_viral_score", "payload": '{"platform":"tiktok","text":"A strong opening hook"}'})) |
|
|
| with gr.Tab("Rendering"): |
| render_json = gr.Textbox( |
| label="Render JSON", |
| lines=14, |
| value="", |
| placeholder="Paste a production render request JSON object with absolute or uploaded asset paths.", |
| ) |
| render_button = gr.Button("Submit Render", variant="primary") |
| render_output = gr.JSON(label="Submission") |
| render_button.click(fn=submit_render_job, inputs=render_json, outputs=render_output, api_name="submit_render_job", api_description=_mcp_description("Submit a validated JSON video render job.", {"payload": '{"scenes":[{"start":0,"duration":5,"media":"clip.mp4","caption":"Opening hook"}],"platform":"tiktok","output_name":"render.mp4"}'})) |
|
|
| with gr.Tab("AI Reels"): |
| script = gr.Textbox(label="Script", lines=6) |
| voiceover = gr.File(label="Voiceover", file_types=["audio"], type="filepath") |
| assets = gr.File(label="Assets", file_count="multiple", type="filepath") |
| template = gr.Dropdown(choices=list_templates(), value="tiktok_classic", label="Caption Template") |
| creative_style = gr.Dropdown(choices=list_creative_styles(), value="viral_shorts", label="Creative Style") |
| platform = gr.Dropdown(choices=list_platform_profiles(), value="tiktok", label="Platform") |
| music = gr.File(label="Background Music", file_types=["audio"], type="filepath") |
| ai_button = gr.Button("Submit AI Reel", variant="primary") |
| ai_output = gr.JSON(label="Submission") |
| ai_button.click(fn=submit_ai_reel, inputs=[script, voiceover, assets, template, creative_style, platform, music], outputs=ai_output, api_name="submit_ai_reel", api_description=_mcp_description("Create an AI reel from a script, voiceover, and visual assets. Upload files first and use the returned paths.", {"script": "Three automation tips.", "voiceover": "/tmp/voice.wav", "assets": ["/tmp/a.jpg", "/tmp/b.mp4"], "template": "tiktok_classic", "creative_style": "viral_shorts", "platform": "tiktok", "music": None})) |
|
|
| with gr.Tab("Batch Render"): |
| batch_json = gr.Textbox(label="Batch JSON", lines=14, value=json.dumps({"jobs": []}, indent=2)) |
| batch_button = gr.Button("Submit Batch", variant="primary") |
| batch_output = gr.JSON(label="Batch Submission") |
| batch_button.click(fn=submit_batch_render, inputs=batch_json, outputs=batch_output, api_name="submit_batch_render", api_description=_mcp_description("Submit multiple render jobs from a JSON batch.", {"payload": '{"jobs":[{"scenes":[{"start":0,"duration":3,"media":"clip.mp4"}],"output_name":"clip-a.mp4"}]}'})) |
|
|
| with gr.Tab("Job Status"): |
| status_job_id = gr.Textbox(label="Job ID") |
| status_button = gr.Button("Refresh") |
| status_output = gr.JSON(label="Status") |
| status_button.click(fn=get_job_status, inputs=status_job_id, outputs=status_output, api_name="get_job_status", api_description=_mcp_description("Retrieve the current state and metrics for a job.", {"job_id": "job_abc123"})) |
|
|
| with gr.Tab("Logs"): |
| logs_job_id = gr.Textbox(label="Job ID") |
| logs_button = gr.Button("Load Logs") |
| logs_output = gr.Textbox(label="Logs", lines=20) |
| logs_button.click(fn=get_job_logs, inputs=logs_job_id, outputs=logs_output, api_name="get_job_logs", api_description=_mcp_description("Retrieve execution logs for a job.", {"job_id": "job_abc123"})) |
|
|
| with gr.Tab("Downloads"): |
| download_job_id = gr.Textbox(label="Job ID") |
| download_button = gr.Button("Get Output") |
| download_output = gr.File(label="Rendered Video") |
| download_button.click(fn=get_download_path, inputs=download_job_id, outputs=download_output, api_name="get_download_path", api_description=_mcp_description("Return the completed artifact path for a job.", {"job_id": "job_abc123"})) |
|
|
| with gr.Tab("Transcribe"): |
| transcribe_audio = gr.File(label="Audio or Video", file_types=["audio", "video"], type="filepath") |
| transcribe_model = gr.Dropdown( |
| choices=["tiny", "base", "small", "medium", "large-v3"], |
| value=settings.whisper_model_size, |
| label="Whisper Model", |
| ) |
| transcribe_language = gr.Textbox(label="Language", placeholder="Optional ISO code, e.g. en") |
| transcribe_button = gr.Button("Transcribe", variant="primary") |
| transcribe_output = gr.JSON(label="Transcript") |
| transcribe_button.click( |
| fn=transcribe_audio_file, |
| inputs=[transcribe_audio, transcribe_model, transcribe_language], |
| outputs=transcribe_output, |
| api_name="transcribe_audio_file", |
| api_description=_mcp_description("Transcribe an uploaded audio or video file with word timestamps. Upload the file first and use its returned path.", {"path": "/tmp/interview.mp3", "model_size": "tiny", "language": "en"}), |
| ) |
|
|
| with gr.Tab("Asset Inspector"): |
| asset_path = gr.Textbox(label="Asset path") |
| inspect_button = gr.Button("Inspect") |
| inspect_output = gr.JSON(label="Metadata") |
| inspect_button.click(fn=inspect_media_asset, inputs=asset_path, outputs=inspect_output, api_name="inspect_media_asset", api_description=_mcp_description("Inspect media codecs, streams, duration, and dimensions.", {"path": "/app/storage/video.mp4"})) |
|
|
| with gr.Tab("AI Analysis"): |
| analysis_media = gr.Textbox(label="Media URL or path") |
| analysis_transcript = gr.Textbox(label="Transcript", lines=5) |
| analysis_platform = gr.Dropdown(choices=list_platform_profiles(), value="tiktok", label="Target Platform") |
| analysis_button = gr.Button("Submit Analysis", variant="primary") |
| analysis_output = gr.JSON(label="Analysis Job") |
| analysis_button.click( |
| fn=submit_media_analysis, |
| inputs=[analysis_media, analysis_transcript, analysis_platform], |
| outputs=analysis_output, |
| api_name="submit_media_analysis", |
| api_description=_mcp_description("Analyze media for highlights, pacing, platform fit, and engagement signals.", {"media": "https://example.com/video.mp4", "transcript": "A strong opening hook and useful explanation.", "platform": "tiktok"}), |
| ) |
|
|
| with gr.Tab("Clip Generator"): |
| clip_media = gr.Textbox(label="Media URL or path") |
| clip_json = gr.Textbox(label="Clip JSON", lines=6, value=json.dumps([{"start": 0, "end": 8}], indent=2)) |
| clip_button = gr.Button("Generate Clips", variant="primary") |
| clip_output = gr.JSON(label="Clip Job") |
| clip_button.click(fn=submit_clip_job, inputs=[clip_media, clip_json], outputs=clip_output, api_name="submit_clip_job", api_description=_mcp_description("Generate one or more clips from media and timed clip JSON.", {"media": "https://example.com/video.mp4", "clips_json": '[{"start":0,"end":8},{"start":20,"end":32}]'})) |
|
|
| with gr.Tab("Metadata"): |
| metadata_topic = gr.Textbox(label="Topic or transcript", lines=5) |
| metadata_platform = gr.Dropdown(choices=list_platform_profiles(), value="tiktok", label="Platform") |
| metadata_button = gr.Button("Generate Metadata", variant="primary") |
| metadata_output = gr.JSON(label="Metadata Job") |
| metadata_button.click(fn=submit_metadata_job, inputs=[metadata_topic, metadata_platform], outputs=metadata_output, api_name="submit_metadata_job", api_description=_mcp_description("Generate platform-aware title, description, hashtags, and chapters.", {"topic": "Five n8n video automation mistakes", "platform": "youtube_shorts"})) |
|
|
| with gr.Tab("Publishing"): |
| publish_media = gr.Textbox(label="Media URL or rendered output path") |
| publish_title = gr.Textbox(label="Title") |
| publish_platforms = gr.Textbox(label="Platforms", value="youtube,tiktok,instagram") |
| publish_button = gr.Button("Create Publish Draft", variant="primary") |
| publish_output = gr.JSON(label="Publish Job") |
| publish_button.click(fn=create_publish_draft, inputs=[publish_media, publish_title, publish_platforms], outputs=publish_output, api_name="create_publish_draft", api_description=_mcp_description("Create a publishing draft for one or more platforms.", {"media": "/app/exports/render.mp4", "title": "Automation Tips", "platforms": "youtube,tiktok,instagram"})) |
|
|
| with gr.Tab("Settings"): |
| settings_button = gr.Button("Load Settings") |
| settings_output = gr.JSON(label="Settings") |
| settings_button.click(fn=get_runtime_settings, outputs=settings_output, api_name="get_runtime_settings", api_description=_mcp_description("Return renderer configuration and runtime capability settings.", {})) |
|
|
| with gr.Tab("Queue Monitor"): |
| queue_button = gr.Button("Refresh Queue") |
| queue_output = gr.JSON(label="Queue") |
| queue_button.click(fn=get_queue_status, outputs=queue_output, api_name="get_queue_status", api_description=_mcp_description("Return queue counts and active render jobs.", {})) |
|
|
| return dashboard |
|
|
|
|
| def submit_render_job(payload: str) -> dict[str, Any]: |
| """Submit a validated JSON timeline for asynchronous video rendering.""" |
| data = apply_creative_style(apply_preset(json.loads(payload))) |
| request = Timeline.request_from_payload(data) |
| job_id = job_manager.submit_render(request) |
| return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"} |
|
|
|
|
| def create_project(name: str, metadata_json: str) -> dict[str, Any]: |
| """Create a persistent studio project with optional JSON metadata.""" |
| metadata = json.loads(metadata_json or "{}") |
| return {"project": project_manager.create(name, metadata=metadata)} |
|
|
|
|
| def list_projects() -> dict[str, Any]: |
| """List all persistent studio projects.""" |
| return {"projects": project_manager.list()} |
|
|
|
|
| def add_project_asset(project_id: str, asset_json: str) -> dict[str, Any]: |
| """Attach a media asset JSON object to an existing project.""" |
| return {"project": project_manager.add_asset(project_id, json.loads(asset_json or "{}"))} |
|
|
|
|
| def add_timeline_item(project_id: str, track_type: str, item_json: str) -> dict[str, Any]: |
| """Add a JSON item to a video, audio, text, overlay, sticker, or subtitle track.""" |
| return {"project": project_manager.add_to_timeline(project_id, json.loads(item_json or "{}"), track_type=track_type)} |
|
|
|
|
| def apply_timeline_operation(project_id: str, operation_json: str) -> dict[str, Any]: |
| """Apply a JSON timeline operation such as split, trim, insert, or ripple delete.""" |
| data = json.loads(operation_json or "{}") |
| return { |
| "project": project_manager.timeline_operation( |
| project_id, |
| data.get("operation", "drag"), |
| item_id=data.get("item_id"), |
| params=data.get("params", {}), |
| ) |
| } |
|
|
|
|
| def _catalog_section(section: str) -> dict[str, Any]: |
| catalog = capability_catalog() |
| return {section: catalog.get(section)} |
|
|
|
|
| def load_template_catalog() -> dict[str, Any]: |
| """Return available render and caption template capabilities.""" |
| return _catalog_section("templates") |
|
|
|
|
| def load_effect_catalog() -> dict[str, Any]: |
| """Return available video effect capabilities.""" |
| return _catalog_section("effects") |
|
|
|
|
| def load_filter_catalog() -> dict[str, Any]: |
| """Return available video filter capabilities.""" |
| return _catalog_section("filters") |
|
|
|
|
| def load_audio_catalog() -> dict[str, Any]: |
| """Return audio-processing and music-generation capabilities.""" |
| catalog = capability_catalog() |
| return {"audio": catalog["audio"], "music": catalog["music_generator"]} |
|
|
|
|
| def submit_caption_job(text: str) -> dict[str, Any]: |
| """Submit an asynchronous caption-generation job from source text.""" |
| from renderer.studio import StudioTaskProcessor |
|
|
| job_id = job_manager.submit_task(lambda task_id, log: StudioTaskProcessor(settings, log=log).caption_generate({"text": text}, task_id)) |
| return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"} |
|
|
|
|
| def submit_ai_tool(tool: str, payload: str) -> dict[str, Any]: |
| """Submit an AI editing tool using its name and a JSON payload.""" |
| from renderer.studio import StudioTaskProcessor |
|
|
| data = json.loads(payload or "{}") |
| job_id = job_manager.submit_task(lambda task_id, log: StudioTaskProcessor(settings, log=log).ai_tool(tool, data, task_id)) |
| return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"} |
|
|
|
|
| def submit_batch_render(payload: str) -> dict[str, Any]: |
| """Submit multiple asynchronous render jobs from a JSON jobs array.""" |
| data = json.loads(payload) |
| requests = [Timeline.request_from_payload(apply_creative_style(apply_preset(job))) for job in data.get("jobs", [])] |
| return {"job_ids": job_manager.submit_batch(requests)} |
|
|
|
|
| def submit_ai_reel( |
| script: str, |
| voiceover: str, |
| assets: list[str], |
| template: str, |
| creative_style: str, |
| platform: str, |
| music: str | None, |
| ) -> dict[str, Any]: |
| """Create an AI reel from a script, voiceover, visual assets, style, and music.""" |
| request = AIReelsRequest( |
| script=script, |
| voiceover=voiceover, |
| assets=assets or [], |
| template=template, |
| creative_style=creative_style, |
| platform=platform, |
| background_music=music, |
| ) |
| job_id = job_manager.submit_ai_reels(request) |
| return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"} |
|
|
|
|
| def get_job_status(job_id: str) -> dict[str, Any]: |
| """Return state, metrics, artifact metadata, and errors for a job ID.""" |
| return job_manager.get(job_id).__dict__ |
|
|
|
|
| def get_job_logs(job_id: str) -> str: |
| """Return execution log messages for a job ID.""" |
| return "\n\n".join(job_manager.get(job_id).logs) |
|
|
|
|
| def get_download_path(job_id: str) -> str | None: |
| """Return the local artifact path when a job has completed.""" |
| record = job_manager.get(job_id) |
| if record.state != "COMPLETED": |
| return None |
| return record.output_path |
|
|
|
|
| def inspect_media_asset(path: str) -> dict[str, Any]: |
| """Inspect a local media asset for streams, codecs, duration, and dimensions.""" |
| return RenderEngine(settings).inspect_asset(path) |
|
|
|
|
| def transcribe_audio_file(path: str, model_size: str, language: str) -> dict[str, Any]: |
| """Transcribe an uploaded audio or video file with optional language selection.""" |
| return RenderEngine(settings).transcribe( |
| path, |
| model_size=model_size, |
| language=language.strip() or None, |
| word_timestamps=True, |
| ) |
|
|
|
|
| def submit_media_analysis(media: str, transcript: str, platform: str) -> dict[str, Any]: |
| """Analyze media for highlights, pacing, retention, engagement, and platform fit.""" |
| from renderer.platform import PlatformProcessor |
|
|
| job_id = job_manager.submit_task( |
| lambda task_id, log: PlatformProcessor(settings, log=log).analyze(media, task_id, transcript=transcript, platform=platform) |
| ) |
| return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"} |
|
|
|
|
| def submit_clip_job(media: str, clips_json: str) -> dict[str, Any]: |
| """Generate timed clips from a media path or URL and a JSON clip specification.""" |
| from renderer.platform import PlatformProcessor |
|
|
| clips = json.loads(clips_json) |
| job_id = job_manager.submit_task(lambda task_id, log: PlatformProcessor(settings, log=log).clips(media, task_id, clips)) |
| return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"} |
|
|
|
|
| def submit_metadata_job(topic: str, platform: str) -> dict[str, Any]: |
| """Generate platform-aware title, description, hashtags, keywords, and chapters.""" |
| from renderer.platform import PlatformProcessor |
|
|
| job_id = job_manager.submit_task(lambda task_id, log: PlatformProcessor(settings, log=log).metadata(task_id, topic=topic, platform=platform)) |
| return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"} |
|
|
|
|
| def create_publish_draft(media: str, title: str, platforms: str) -> dict[str, Any]: |
| """Create an asynchronous publishing draft for comma-separated platforms.""" |
| from renderer.platform import PlatformProcessor |
|
|
| payload = {"media": media, "title": title, "platforms": [item.strip() for item in platforms.split(",") if item.strip()], "draft": True} |
| job_id = job_manager.submit_task(lambda task_id, log: PlatformProcessor(settings, log=log).publish(payload, task_id)) |
| return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"} |
|
|
|
|
| def get_queue_status() -> dict[str, Any]: |
| """Return queue totals, state counts, worker limits, and active jobs.""" |
| return job_manager.summary() |
|
|
|
|
| def get_runtime_settings() -> dict[str, Any]: |
| """Return renderer paths, worker settings, Whisper settings, and capabilities.""" |
| return { |
| "product": "Ava2lon Studio AI", |
| "base_dir": str(settings.base_dir), |
| "temp_dir": str(settings.temp_dir), |
| "exports_dir": str(settings.exports_dir), |
| "storage_dir": str(settings.storage_dir), |
| "max_workers": settings.max_workers, |
| "whisper_model_size": settings.whisper_model_size, |
| "whisper_device": settings.whisper_device, |
| "capabilities": capability_catalog()["principles"], |
| } |
|
|
|
|
| app = gr.mount_gradio_app( |
| api, |
| create_dashboard(), |
| path="/dashboard", |
| root_path="/dashboard", |
| mcp_server=True, |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |
|
|