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
4 changes: 4 additions & 0 deletions src/praisonai/praisonai/claw/default_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

import os
import praisonaiui as aiui
from praisonai.ui._aiui_datastore import PraisonAISessionDataStore

# ── Set up datastore bridge ─────────────────────────────────
aiui.set_datastore(PraisonAISessionDataStore())

# ── Dashboard style ─────────────────────────────────────────
aiui.set_style("dashboard")
Expand Down
35 changes: 19 additions & 16 deletions src/praisonai/praisonai/cli/commands/realtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,33 @@ def realtime_main(
ctx: typer.Context,
model: Optional[str] = typer.Option(None, "--model", "-m", help="LLM model to use"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

--verbose/-v is now a silent no-op.

After routing to _launch_aiui_app, the verbose parameter is accepted but never read or forwarded. Users passing -v will get no additional output and no error, which is surprising. Either wire it through to _launch_aiui_app (e.g., set a PRAISONAI_LOG_LEVEL env var before launching) or drop the option to avoid misleading users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/cli/commands/realtime.py` at line 18, The verbose
flag defined in the CLI handler is accepted but never used; update the command
to forward the verbose boolean into the launch path by setting the
PRAISONAI_LOG_LEVEL (or equivalent) before calling _launch_aiui_app so the child
app respects -v/--verbose: if verbose is True set PRAISONAI_LOG_LEVEL to a
verbose value (e.g., "DEBUG" or "INFO") and export it to the environment, then
call _launch_aiui_app with the same behavior, or alternatively remove the
verbose option entirely; ensure you modify the CLI function that declares
verbose and the call site of _launch_aiui_app to either read the env var or
accept an explicit log-level argument so the flag is not a silent no-op.

port: int = typer.Option(8085, "--port", "-p", help="Port for realtime UI"),
):
"""
Start realtime interaction mode.

Now routes to the new aiui-based realtime interface.

Examples:
praisonai realtime
praisonai realtime --model gpt-4o
praisonai realtime --model gpt-4o --port 9000
"""
from praisonai.cli.main import PraisonAI
import sys
# Route to new UI realtime subcommand
from praisonai.cli.commands.ui import _launch_aiui_app
import os

argv = ['realtime']
if model:
argv.extend(['--model', model])
if verbose:
argv.append('--verbose')
os.environ["MODEL_NAME"] = model

original_argv = sys.argv
sys.argv = ['praisonai'] + argv
print("🎤 Launching PraisonAI Realtime Voice Interface...")
print("Note: Migrated from Chainlit to aiui. Full WebRTC voice coming soon.")

try:
praison = PraisonAI()
praison.main()
except SystemExit:
pass
finally:
sys.argv = original_argv
_launch_aiui_app(
app_dir="ui_realtime",
default_app_name="ui_realtime",
port=port,
host="0.0.0.0",
app_file=None,
reload=False,
ui_name="Realtime Voice"
)
123 changes: 123 additions & 0 deletions src/praisonai/praisonai/cli/commands/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove extraneous f prefix.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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")
🧰 Tools
🪛 Ruff (0.15.10)

[error] 56-56: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/cli/commands/ui.py` at line 56, Remove the
unnecessary f-string prefix on the print statement that logs the UI install
error: change the print(f"\n\033[91mERROR: PraisonAI UI (aiui) is not
installed.\033[0m") to a plain string print("\n\033[91mERROR: PraisonAI UI
(aiui) is not installed.\033[0m"); this targets the print call containing
"ERROR: PraisonAI UI (aiui) is not installed." (and then re-run the linter to
ensure F541 is resolved).

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,
Expand All @@ -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
Expand Down Expand Up @@ -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")
26 changes: 25 additions & 1 deletion src/praisonai/praisonai/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Default aiui host should not expose all network interfaces.

Line 5315 binds to 0.0.0.0, which exposes the dashboard broadly by default. For a local CLI launch, defaulting to loopback is safer and still allows explicit override.

🔧 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_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,
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=default_host,
app_file=None,
🧰 Tools
🪛 Ruff (0.15.10)

[error] 5315-5315: Possible binding to all interfaces

(S104)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/cli/main.py` around lines 5311 - 5316, The CLI
currently calls _launch_aiui_app with host="0.0.0.0", which exposes the UI on
all interfaces; change the default host value passed to _launch_aiui_app to
"127.0.0.1" so the dashboard binds to loopback by default (while keeping the
existing host parameter so callers can still explicitly override it), i.e.,
update the invocation that sets host to "0.0.0.0" to use "127.0.0.1" instead
(refer to the _launch_aiui_app call site in main.py).

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.
Expand Down
108 changes: 108 additions & 0 deletions src/praisonai/praisonai/ui/_aiui_datastore.py
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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-agents

Repository: MervinPraison/PraisonAI

Length of output: 30097


add_message is async but calls sync store methods.

self._store.add_message(...) is invoked synchronously inside an async def. If the underlying store performs I/O (file/Redis/Mongo), this blocks the event loop. Use await asyncio.to_thread(self._store.add_message, …) when the store is sync, or extend SessionStoreProtocol with async variants (add_message_async, etc.) if available.

Additionally, create_session() only constructs a dict and returns — nothing is written to the store. Since SessionStoreProtocol lacks a create_session hook, stores requiring explicit session creation will not persist the session on the wrapper's create_session() call. Calling get_messages() immediately after would return empty results. Document this lazy-creation invariant or add an explicit creation hook to SessionStoreProtocol.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/ui/_aiui_datastore.py` around lines 83 - 90,
add_message is async but invokes the synchronous store method
self._store.add_message which can block the event loop; wrap the sync call with
await asyncio.to_thread(self._store.add_message, session_id, role=...,
content=..., metadata=...) or alternatively extend SessionStoreProtocol with an
async add_message_async and call await self._store.add_message_async(...). Also
address create_session which currently only returns a dict without persisting:
either add a create_session(session_id, metadata) hook to SessionStoreProtocol
and call it from create_session, or document the lazy-creation behavior so
callers know they must persist the session before calling get_messages.


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 []
1 change: 1 addition & 0 deletions src/praisonai/praisonai/ui_agents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""UI Agents module - YAML agents runner interface."""
Loading