Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions massgen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3930,16 +3930,18 @@ async def main(args):

# Register new session immediately (before first turn runs)
# Get log directory for session metadata
from massgen.logger_config import get_log_session_root
from massgen.logger_config import get_log_session_dir, get_log_session_root
from massgen.session import SessionRegistry

log_dir = get_log_session_root()
log_dir_name = log_dir.name

# Print LOG_DIR for automation mode (LLM agents need this to monitor progress)
# LOG_DIR is the main session directory, STATUS includes turn/attempt subdirectory
if args.automation:
full_log_dir = get_log_session_dir()
print(f"LOG_DIR: {log_dir}")
print(f"STATUS: {log_dir / 'status.json'}")
print(f"STATUS: {full_log_dir / 'status.json'}")

registry = SessionRegistry()
registry.register_session(
Expand Down Expand Up @@ -4192,6 +4194,11 @@ def cli_main():
default="127.0.0.1",
help="Host for web UI server (default: 127.0.0.1)",
)
parser.add_argument(
"--no-browser",
action="store_true",
help="Don't auto-open browser when using --web with a question",
)
parser.add_argument(
"--automation",
action="store_true",
Expand Down Expand Up @@ -4512,14 +4519,50 @@ def cli_main():
from .frontend.web import run_server

config_path = args.config if hasattr(args, "config") and args.config else None
question = getattr(args, "question", None)
automation_mode = getattr(args, "automation", False)

print(f"{BRIGHT_CYAN}🌐 Starting MassGen Web UI...{RESET}")
print(f"{BRIGHT_GREEN} Server: http://{args.web_host}:{args.web_port}{RESET}")
if config_path:
print(f"{BRIGHT_GREEN} Config: {config_path}{RESET}")
else:
print(f"{BRIGHT_YELLOW} No config specified - use --config or select in UI{RESET}")

# Build auto-launch URL if question is provided
auto_url = None
if question:
import urllib.parse

prompt_encoded = urllib.parse.quote(question)
auto_url = f"http://{args.web_host}:{args.web_port}/?prompt={prompt_encoded}"
if config_path:
config_encoded = urllib.parse.quote(config_path)
auto_url += f"&config={config_encoded}"
print(f"{BRIGHT_GREEN} Auto-launch URL: {auto_url}{RESET}")

if automation_mode:
print(f"{BRIGHT_YELLOW} Automation mode enabled - progress visible in web UI{RESET}")
print(f"{BRIGHT_CYAN} Status files: .massgen/massgen_logs/log_<timestamp>/turn_N/attempt_N/status.json{RESET}")
if not question:
print(f"{BRIGHT_YELLOW} (no question provided - use web UI to start coordination){RESET}")

print(f"{BRIGHT_YELLOW} Press Ctrl+C to stop{RESET}\n")
run_server(host=args.web_host, port=args.web_port, config_path=config_path)

# Auto-open browser if question+config provided (unless --no-browser or automation mode)
no_browser = getattr(args, "no_browser", False)
if auto_url and config_path and not no_browser and not automation_mode:
import threading
import webbrowser

def open_browser():
import time

time.sleep(0.5) # Wait for server to start
webbrowser.open(auto_url)

threading.Thread(target=open_browser, daemon=True).start()
run_server(host=args.web_host, port=args.web_port, config_path=config_path, automation_mode=automation_mode)
except ImportError as e:
print(f"{BRIGHT_RED}❌ Web UI dependencies not installed.{RESET}")
print(f"{BRIGHT_CYAN} Run: pip install massgen{RESET}")
Expand Down
57 changes: 50 additions & 7 deletions massgen/frontend/displays/silent_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"""

import time
from pathlib import Path
from typing import Optional

from .base_display import BaseDisplay
Expand Down Expand Up @@ -44,20 +43,22 @@ def initialize(self, question: str, log_filename: Optional[str] = None):
"""Initialize the display with essential information only.

Prints:
- LOG_DIR: Full path to log directory
- STATUS: Path to status.json file for real-time monitoring
- QUESTION: The question being answered

Note: LOG_DIR and STATUS paths are printed by cli.py for automation mode.

Args:
question: The user's question
log_filename: Path to the main log file (used to determine log directory)
"""
self.start_time = time.time()

if log_filename:
self.log_dir = Path(log_filename).parent
print(f"LOG_DIR: {self.log_dir}")
print(f"STATUS: {self.log_dir / 'status.json'}")
# Store log dir for internal use (paths already printed by cli.py)
from massgen.logger_config import get_log_session_dir

log_session_dir = get_log_session_dir()
if log_session_dir:
self.log_dir = log_session_dir

print(f"QUESTION: {question}")
print("[Coordination in progress - monitor status.json for real-time updates]")
Expand Down Expand Up @@ -106,13 +107,52 @@ def add_orchestrator_event(self, event: str):
self.orchestrator_events.append(event)
# Silent - no output to stdout

def _print_timeline(self, vote_results):
"""Print full timeline with answers, votes, and results.

Args:
vote_results: Dictionary containing vote data including:
- vote_counts: {agent_id: vote_count}
- voter_details: {voted_for_agent_id: [{voter: voter_id, reason: str}]}
- winner: winning agent_id
- is_tie: boolean
"""
if not vote_results:
return

print("\nTIMELINE:")

# Show individual votes from voter_details
voter_details = vote_results.get("voter_details", {})
if voter_details:
for voted_for, voters in voter_details.items():
for voter_info in voters:
voter = voter_info.get("voter", "unknown")
reason = voter_info.get("reason", "")
reason_preview = reason[:50] + "..." if len(reason) > 50 else reason
print(f" [VOTE] {voter} -> {voted_for}")
if reason_preview:
print(f" Reason: {reason_preview}")

# Show vote distribution summary
vote_counts = vote_results.get("vote_counts", {})
if vote_counts:
print(" [RESULTS]")
winner = vote_results.get("winner")
is_tie = vote_results.get("is_tie", False)
for agent, count in sorted(vote_counts.items(), key=lambda x: -x[1]):
winner_mark = " (winner)" if agent == winner else ""
tie_mark = " (tie-broken)" if is_tie and agent == winner else ""
print(f" {agent}: {count} vote{'s' if count != 1 else ''}{winner_mark}{tie_mark}")

def show_final_answer(self, answer: str, vote_results=None, selected_agent=None):
"""Display the final coordinated answer with essential information.

Prints:
- WINNER: The winning agent ID
- ANSWER_FILE: Path to final answer file
- DURATION: Total coordination time
- TIMELINE: Answer submissions, votes, and results
- ANSWER_PREVIEW: First 200 characters of answer

Args:
Expand All @@ -133,6 +173,9 @@ def show_final_answer(self, answer: str, vote_results=None, selected_agent=None)
duration = time.time() - self.start_time
print(f"DURATION: {duration:.1f}s")

# Print full timeline
self._print_timeline(vote_results)

# Show preview of answer (first 200 chars)
if answer:
preview_length = 200
Expand Down
16 changes: 16 additions & 0 deletions massgen/frontend/displays/web_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ def initialize(self, question: str, log_filename: Optional[str] = None) -> None:
question: The coordination question
log_filename: Optional log file path
"""
self.question = question # Store for snapshot restoration

# Print status.json location to terminal for automation monitoring
# Use get_log_session_dir() to get the actual path with turn/attempt subdirectories
try:
from massgen.logger_config import get_log_session_dir

log_session_dir = get_log_session_dir()
if log_session_dir:
print(f"[WebUI] LOG_DIR: {log_session_dir}")
print(f"[WebUI] STATUS: {log_session_dir / 'status.json'}")
except Exception:
pass # Silently ignore if logger not configured

self._emit(
"init",
{
Expand Down Expand Up @@ -703,7 +717,9 @@ def get_state_snapshot(self) -> Dict[str, Any]:
"""
return {
"session_id": self.session_id,
"question": getattr(self, "question", ""),
"agents": self.agent_ids,
"agent_models": self.agent_models,
"agent_status": dict(self.agent_status),
"agent_outputs": {agent_id: list(outputs) for agent_id, outputs in self.agent_outputs.items()},
"vote_distribution": dict(self._vote_distribution),
Expand Down
Loading