-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: Replace all remaining Chainlit usage with PraisonAIUI (aiui) and wire native DB/sessions #1444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Replace all remaining Chainlit usage with PraisonAIUI (aiui) and wire native DB/sessions #1444
Changes from all commits
065f72a
523ca60
12cafe5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -37,6 +37,71 @@ def _ensure_default_app() -> Path: | |||||
| return DEFAULT_APP | ||||||
|
|
||||||
|
|
||||||
| def _launch_aiui_app( | ||||||
| app_dir: str, | ||||||
| default_app_name: str, | ||||||
| port: int, | ||||||
| host: str, | ||||||
| app_file: Optional[str], | ||||||
| reload: bool, | ||||||
| ui_name: str | ||||||
| ) -> None: | ||||||
| """Common function to launch aiui apps.""" | ||||||
| # 1. Check praisonaiui is installed | ||||||
| try: | ||||||
| import importlib.util | ||||||
| if importlib.util.find_spec("praisonaiui") is None: | ||||||
| raise ImportError | ||||||
| except ImportError: | ||||||
| print(f"\n\033[91mERROR: PraisonAI UI (aiui) is not installed.\033[0m") | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove extraneous No interpolation placeholders in this string; ruff flags F541. - print(f"\n\033[91mERROR: PraisonAI UI (aiui) is not installed.\033[0m")
+ print("\n\033[91mERROR: PraisonAI UI (aiui) is not installed.\033[0m")📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.10)[error] 56-56: f-string without any placeholders Remove extraneous (F541) 🤖 Prompt for AI Agents |
||||||
| print('\nInstall with:\n pip install "praisonai[ui]"\n') | ||||||
| sys.exit(1) | ||||||
|
|
||||||
| # 2. Resolve app file | ||||||
| if app_file: | ||||||
| resolved = Path(app_file) | ||||||
| if not resolved.exists(): | ||||||
| print(f"\033[91mERROR: App file not found: {app_file}\033[0m") | ||||||
| sys.exit(1) | ||||||
| else: | ||||||
| ui_dir = Path.home() / ".praisonai" / app_dir | ||||||
| default_app = ui_dir / "app.py" | ||||||
|
|
||||||
| # Ensure default app exists | ||||||
| if not default_app.exists(): | ||||||
| ui_dir.mkdir(parents=True, exist_ok=True) | ||||||
| bundled = Path(__file__).parent.parent.parent / default_app_name / "default_app.py" | ||||||
| if not bundled.exists(): | ||||||
| print(f"\033[91mERROR: Bundled default_app.py not found at {bundled}\033[0m") | ||||||
| sys.exit(1) | ||||||
| default_app.write_text(bundled.read_text()) | ||||||
| print(f" ✓ Created default {ui_name} config: {default_app}") | ||||||
|
|
||||||
| resolved = default_app | ||||||
|
|
||||||
| # 3. Launch via aiui run | ||||||
| import subprocess | ||||||
|
|
||||||
| cmd = ["aiui", "run", str(resolved), "--port", str(port), "--host", host] | ||||||
| if reload: | ||||||
| cmd.append("--reload") | ||||||
|
|
||||||
| print(f"\n🤖 PraisonAI {ui_name} starting at http://{host}:{port}") | ||||||
| print(f" App: {resolved}\n") | ||||||
|
|
||||||
| try: | ||||||
| subprocess.run(cmd, check=True) | ||||||
| except FileNotFoundError: | ||||||
| # Fallback: python -m praisonaiui.cli | ||||||
| cmd = [sys.executable, "-m", "praisonaiui.cli", "run", str(resolved), | ||||||
| "--port", str(port), "--host", host] | ||||||
| if reload: | ||||||
| cmd.append("--reload") | ||||||
| subprocess.run(cmd, check=True) | ||||||
| except KeyboardInterrupt: | ||||||
| print(f"\n🤖 {ui_name} stopped.") | ||||||
|
|
||||||
|
|
||||||
| @app.callback(invoke_without_command=True) | ||||||
| def ui( | ||||||
| ctx: typer.Context, | ||||||
|
|
@@ -60,10 +125,14 @@ def ui( | |||||
| praisonai ui | ||||||
| praisonai ui --port 9000 | ||||||
| praisonai ui --app my-chat.py | ||||||
| praisonai ui agents # YAML agents dashboard | ||||||
| praisonai ui bot # Bot interface | ||||||
| praisonai ui realtime # Voice realtime | ||||||
| """ | ||||||
| if ctx.invoked_subcommand is not None: | ||||||
| return | ||||||
|
|
||||||
| # Use legacy implementation for backward compatibility | ||||||
| # 1. Check praisonaiui is installed | ||||||
| try: | ||||||
| import importlib.util | ||||||
|
|
@@ -104,3 +173,57 @@ def ui( | |||||
| subprocess.run(cmd, check=True) | ||||||
| except KeyboardInterrupt: | ||||||
| print("\n🤖 Chat stopped.") | ||||||
|
|
||||||
|
|
||||||
| @app.command() | ||||||
| def agents( | ||||||
| port: int = typer.Option(8083, "--port", "-p", help="Port to run agents UI on"), | ||||||
| host: str = typer.Option("0.0.0.0", "--host", help="Host to bind to"), | ||||||
| app_file: Optional[str] = typer.Option( | ||||||
| None, "--app", "-a", help="Custom app.py file" | ||||||
| ), | ||||||
| reload: bool = typer.Option(False, "--reload", "-r", help="Enable auto-reload"), | ||||||
| ): | ||||||
| """ | ||||||
| Launch YAML Agents Dashboard. | ||||||
|
|
||||||
| Replaces the old Chainlit agents interface with aiui. | ||||||
| Loads agents from agents.yaml in the current directory. | ||||||
| """ | ||||||
| _launch_aiui_app("ui_agents", "ui_agents", port, host, app_file, reload, "Agents Dashboard") | ||||||
|
|
||||||
|
|
||||||
| @app.command() | ||||||
| def bot( | ||||||
| port: int = typer.Option(8084, "--port", "-p", help="Port to run bot UI on"), | ||||||
| host: str = typer.Option("0.0.0.0", "--host", help="Host to bind to"), | ||||||
| app_file: Optional[str] = typer.Option( | ||||||
| None, "--app", "-a", help="Custom app.py file" | ||||||
| ), | ||||||
| reload: bool = typer.Option(False, "--reload", "-r", help="Enable auto-reload"), | ||||||
| ): | ||||||
| """ | ||||||
| Launch Bot Interface. | ||||||
|
|
||||||
| Replaces the old Chainlit bot interface with aiui. | ||||||
| Provides step-by-step interaction visualization. | ||||||
| """ | ||||||
| _launch_aiui_app("ui_bot", "ui_bot", port, host, app_file, reload, "Bot Interface") | ||||||
|
|
||||||
|
|
||||||
| @app.command() | ||||||
| def realtime( | ||||||
| port: int = typer.Option(8085, "--port", "-p", help="Port to run realtime UI on"), | ||||||
| host: str = typer.Option("0.0.0.0", "--host", help="Host to bind to"), | ||||||
| app_file: Optional[str] = typer.Option( | ||||||
| None, "--app", "-a", help="Custom app.py file" | ||||||
| ), | ||||||
| reload: bool = typer.Option(False, "--reload", "-r", help="Enable auto-reload"), | ||||||
| ): | ||||||
| """ | ||||||
| Launch Realtime Voice Interface (Beta). | ||||||
|
|
||||||
| Replaces the old Chainlit realtime interface with aiui. | ||||||
| Note: Full WebRTC voice is pending PraisonAIUI implementation. | ||||||
| """ | ||||||
| _launch_aiui_app("ui_realtime", "ui_realtime", port, host, app_file, reload, "Realtime Voice") | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -726,7 +726,10 @@ def __init__(self): | |||||||||||||||||||||||||||
| if args.ui == "gradio": | ||||||||||||||||||||||||||||
| self.create_gradio_interface() | ||||||||||||||||||||||||||||
| elif args.ui == "chainlit": | ||||||||||||||||||||||||||||
| self.create_chainlit_interface() | ||||||||||||||||||||||||||||
| # Deprecation warning and route to new aiui agents interface | ||||||||||||||||||||||||||||
| print("\n\033[93mWARNING: --ui chainlit is deprecated and will be removed in a future release.\033[0m") | ||||||||||||||||||||||||||||
| print("Launching the new aiui-based agents interface instead...") | ||||||||||||||||||||||||||||
| self.create_aiui_agents_interface() | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| # Modify code to allow default UI | ||||||||||||||||||||||||||||
| AgentsGenerator = _get_agents_generator() | ||||||||||||||||||||||||||||
|
|
@@ -5296,6 +5299,27 @@ def create_realtime_interface(self): | |||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| print("ERROR: Realtime UI is not installed. Please install it with 'pip install \"praisonai[realtime]\"' to use the realtime UI.") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def create_aiui_agents_interface(self): | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| Create an aiui-based agents interface (replaces Chainlit). | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Routes to the new `praisonai ui agents` subcommand. | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| from praisonai.cli.commands.ui import _launch_aiui_app | ||||||||||||||||||||||||||||
| print("🤖 Launching PraisonAI Agents Dashboard (aiui)...") | ||||||||||||||||||||||||||||
| _launch_aiui_app( | ||||||||||||||||||||||||||||
| app_dir="ui_agents", | ||||||||||||||||||||||||||||
| default_app_name="ui_agents", | ||||||||||||||||||||||||||||
| port=8082, # Use same port as old Chainlit agents | ||||||||||||||||||||||||||||
| host="0.0.0.0", | ||||||||||||||||||||||||||||
| app_file=None, | ||||||||||||||||||||||||||||
|
Comment on lines
+5311
to
+5316
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Default aiui host should not expose all network interfaces. Line 5315 binds to 🔧 Proposed fix def create_aiui_agents_interface(self):
"""
Create an aiui-based agents interface (replaces Chainlit).
Routes to the new `praisonai ui agents` subcommand.
"""
try:
from praisonai.cli.commands.ui import _launch_aiui_app
print("🤖 Launching PraisonAI Agents Dashboard (aiui)...")
+ default_host = os.environ.get("PRAISONAI_UI_HOST", "127.0.0.1")
_launch_aiui_app(
app_dir="ui_agents",
default_app_name="ui_agents",
port=8082, # Use same port as old Chainlit agents
- host="0.0.0.0",
+ host=default_host,
app_file=None,
reload=False,
ui_name="Agents Dashboard"
)📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.10)[error] 5315-5315: Possible binding to all interfaces (S104) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| reload=False, | ||||||||||||||||||||||||||||
| ui_name="Agents Dashboard" | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| except ImportError: | ||||||||||||||||||||||||||||
| print("ERROR: PraisonAI UI (aiui) is not installed. Please install it with 'pip install \"praisonai[ui]\"' to use the agents dashboard.") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def handle_context_command(self, url: str, goal: str, auto_analyze: bool = False) -> str: | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| Handle the context command by creating a ContextAgent and running it. | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| """Bridge praisonaiagents SessionStore → aiui BaseDataStore. | ||
|
|
||
| Allows any SessionStoreProtocol implementation (file/Redis/Mongo) to back | ||
| the aiui dashboard's Sessions page. No Chainlit required. | ||
| """ | ||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| import uuid | ||
| from typing import Any, Optional | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # Fail loudly on missing optional dependencies per AGENTS.md §4.2 | ||
| try: | ||
| from praisonaiui.datastore import BaseDataStore | ||
| except ImportError as e: | ||
| raise ImportError( | ||
| "praisonaiui is required for PraisonAISessionDataStore. " | ||
| "Install with: pip install 'praisonai[ui]'" | ||
| ) from e | ||
|
|
||
| try: | ||
| from praisonaiagents.session import SessionStoreProtocol | ||
| from praisonaiagents.session import get_hierarchical_session_store | ||
| except ImportError as e: | ||
| raise ImportError( | ||
| "praisonaiagents is required for PraisonAISessionDataStore. " | ||
| "Install with: pip install praisonaiagents" | ||
| ) from e | ||
|
|
||
|
|
||
| class PraisonAISessionDataStore(BaseDataStore): | ||
| """Adapter that bridges PraisonAI SessionStoreProtocol to aiui BaseDataStore.""" | ||
|
|
||
| def __init__(self, store: Optional[SessionStoreProtocol] = None): | ||
| """Initialize with an optional session store, defaults to hierarchical store.""" | ||
| self._store = store or get_hierarchical_session_store() | ||
|
|
||
| def _new_id(self) -> str: | ||
| """Generate a new session ID.""" | ||
| return str(uuid.uuid4()) | ||
|
|
||
| async def list_sessions(self) -> list[dict[str, Any]]: | ||
| """List all available sessions.""" | ||
| # Check if store supports listing (DefaultSessionStore/HierarchicalSessionStore do) | ||
| list_fn = getattr(self._store, "list_sessions", None) | ||
| if list_fn is None: | ||
| return [] # Protocol implementation doesn't support listing | ||
|
|
||
| try: | ||
| # DefaultSessionStore/HierarchicalSessionStore return list[dict] | ||
| return list_fn(limit=50) or [] | ||
| except Exception: | ||
| logger.exception("Failed to list sessions") | ||
| return [] | ||
|
|
||
| async def get_session(self, session_id: str) -> Optional[dict[str, Any]]: | ||
| """Get a specific session by ID.""" | ||
| if not self._store.session_exists(session_id): | ||
| return None | ||
|
|
||
| try: | ||
| chat_history = self._store.get_chat_history(session_id) | ||
| return { | ||
| "id": session_id, | ||
| "messages": chat_history or [], | ||
| } | ||
| except Exception: | ||
| logger.exception("Failed to load session %s", session_id) | ||
| return None | ||
|
|
||
| async def create_session(self, session_id: Optional[str] = None) -> dict[str, Any]: | ||
| """Create a new session.""" | ||
| sid = session_id or self._new_id() | ||
| # Sessions are created lazily on first add_message | ||
| return { | ||
| "id": sid, | ||
| "messages": [] | ||
| } | ||
|
|
||
| async def delete_session(self, session_id: str) -> bool: | ||
| """Delete a session and return success status.""" | ||
| try: | ||
| return self._store.delete_session(session_id) | ||
| except Exception: | ||
| logger.exception("Failed to delete session %s", session_id) | ||
| return False | ||
|
|
||
| async def add_message(self, session_id: str, message: dict[str, Any]): | ||
| """Add a message to a session.""" | ||
| self._store.add_message( | ||
| session_id=session_id, | ||
| role=message.get("role", "user"), | ||
| content=message.get("content", ""), | ||
| metadata=message.get("metadata") | ||
| ) | ||
|
Comment on lines
+90
to
+97
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect SessionStoreProtocol to see whether add_message / create_session are sync or async,
# and whether a create/list method exists.
ast-grep --pattern 'class SessionStoreProtocol($$$):
$$$'
rg -nP --type=py -C3 'class\s+SessionStoreProtocol|def\s+(add_message|session_exists|get_chat_history|delete_session|create_session|list_sessions)\b' src/praisonai-agentsRepository: MervinPraison/PraisonAI Length of output: 30097
Additionally, 🤖 Prompt for AI Agents |
||
|
|
||
| async def get_messages(self, session_id: str) -> list[dict[str, Any]]: | ||
| """Get all messages for a session.""" | ||
| if not self._store.session_exists(session_id): | ||
| return [] | ||
|
|
||
| try: | ||
| return self._store.get_chat_history(session_id) or [] | ||
| except Exception: | ||
| logger.exception("Failed to load messages for session %s", session_id) | ||
| return [] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """UI Agents module - YAML agents runner interface.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--verbose/-vis now a silent no-op.After routing to
_launch_aiui_app, theverboseparameter is accepted but never read or forwarded. Users passing-vwill get no additional output and no error, which is surprising. Either wire it through to_launch_aiui_app(e.g., set aPRAISONAI_LOG_LEVELenv var before launching) or drop the option to avoid misleading users.🤖 Prompt for AI Agents