Compare commits

...
1 Commits
Author SHA1 Message Date
Mervin Praison f7b66a52b7 Release v2.3.79 2025-12-23 07:27:35 +00:00
12 changed files with 417 additions and 127 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.3.78" \
"praisonai>=2.3.79" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.3.78" \
"praisonai>=2.3.79" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.3.78" \
"praisonai>=2.3.79" \
"praisonai[ui]" \
"praisonai[crewai]"
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.78.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.78.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.79.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.79.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
@@ -177,12 +177,13 @@ def save_compare_result(result: CompareResult, path: str) -> bool:
return False
def format_comparison_table(result: CompareResult) -> str:
def format_comparison_table(result: CompareResult, show_responses: bool = True) -> str:
"""
Format comparison result as a table string.
Format comparison result as a table string with optional response display.
Args:
result: CompareResult to format
show_responses: Whether to show actual responses from each mode
Returns:
Formatted table string
@@ -190,10 +191,12 @@ def format_comparison_table(result: CompareResult) -> str:
try:
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from io import StringIO
console = Console(file=StringIO(), force_terminal=True)
# Main comparison table
table = Table(title=f"Comparison: {result.query[:50]}...")
table.add_column("Mode", style="cyan")
table.add_column("Time (ms)", style="green")
@@ -224,6 +227,26 @@ def format_comparison_table(result: CompareResult) -> str:
)
console.print(table)
# Show responses if enabled
if show_responses:
console.print("\n[bold cyan]📝 Responses:[/bold cyan]")
for c in result.comparisons:
if c.error is None:
# Truncate long responses
response_preview = c.output[:500] + "..." if len(c.output) > 500 else c.output
console.print(Panel(
response_preview,
title=f"[bold]{c.mode}[/bold] ({c.execution_time_ms:.0f}ms)",
border_style="cyan"
))
else:
console.print(Panel(
f"[red]Error: {c.error}[/red]",
title=f"[bold]{c.mode}[/bold]",
border_style="red"
))
return console.file.getvalue()
except ImportError:
lines = [f"Comparison: {result.query}"]
@@ -231,6 +254,8 @@ def format_comparison_table(result: CompareResult) -> str:
for c in result.comparisons:
status = "OK" if c.error is None else f"ERROR: {c.error}"
lines.append(f"{c.mode}: {c.execution_time_ms:.1f}ms - {status}")
if show_responses and c.error is None:
lines.append(f" Response: {c.output[:200]}...")
return "\n".join(lines)
@@ -70,17 +70,46 @@ class FastContextHandler(FlagHandler):
return True, ""
def _get_folder_tree(self, path: str, max_depth: int = 2) -> str:
"""Get folder tree structure for context."""
tree_lines = []
path = os.path.abspath(path)
def _count_files_quick(self, path: str, max_count: int = 1000) -> int:
"""Quickly count files in directory, stopping at max_count."""
count = 0
ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.cache', 'dist', 'build'}
ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.tox', 'dist', 'build', '.egg-info'}
ignore_exts = {'.pyc', '.pyo', '.so', '.o', '.a', '.dylib'}
for root, dirs, files in os.walk(path):
dirs[:] = [d for d in dirs if d not in ignore_dirs and not d.startswith('.')]
count += len(files)
if count >= max_count:
return count
return count
def _get_folder_tree(self, path: str, max_depth: int = 2) -> str:
"""
Get a compact folder tree structure for context.
Optimized for large directories:
- Limits depth to max_depth (default 2)
- Shows max 10 files per directory
- Truncates total output to 50 lines
- Skips common non-code directories
"""
# Quick check for very large directories
file_count = self._count_files_quick(path, 500)
if file_count >= 500:
# For large directories, reduce depth and show summary
max_depth = 1
logger.debug(f"Large directory detected ({file_count}+ files), reducing tree depth")
tree_lines = []
ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.cache', 'dist', 'build', 'coverage', '.pytest_cache', '.mypy_cache', 'eggs', '*.egg-info'}
ignore_exts = {'.pyc', '.pyo', '.so', '.o', '.a', '.dylib', '.class', '.jar'}
total_files = 0
max_total_files = 100 # Stop after seeing this many files
def walk_tree(current_path, prefix="", depth=0):
if depth > max_depth:
nonlocal total_files
if depth > max_depth or total_files >= max_total_files:
return
try:
entries = sorted(os.listdir(current_path))
except PermissionError:
@@ -98,14 +127,21 @@ class FastContextHandler(FlagHandler):
if ext not in ignore_exts and not entry.startswith('.'):
files.append(entry)
for i, d in enumerate(dirs):
is_last = (i == len(dirs) - 1) and not files
# Limit directories shown at each level
dirs_to_show = dirs[:15]
for i, d in enumerate(dirs_to_show):
is_last = (i == len(dirs_to_show) - 1) and not files
tree_lines.append(f"{prefix}{'└── ' if is_last else '├── '}{d}/")
walk_tree(os.path.join(current_path, d),
prefix + (' ' if is_last else '│ '), depth + 1)
if len(dirs) > 15:
tree_lines.append(f"{prefix} ... and {len(dirs) - 15} more directories")
for i, f in enumerate(files[:10]): # Limit files shown
is_last = i == len(files[:10]) - 1
# Limit files shown
files_to_show = files[:10]
total_files += len(files)
for i, f in enumerate(files_to_show):
is_last = i == len(files_to_show) - 1
tree_lines.append(f"{prefix}{'└── ' if is_last else '├── '}{f}")
if len(files) > 10:
tree_lines.append(f"{prefix} ... and {len(files) - 10} more files")
+35 -16
View File
@@ -72,11 +72,11 @@ class HandoffHandler(FlagHandler):
self.print_status(msg, "error")
return []
from praisonaiagents import Agent, Handoff
from praisonaiagents import Agent, handoff
agents = []
# Create agents for each role
# First pass: Create all agents without handoffs
for i, name in enumerate(agent_names):
agent_config = {
"name": name.title().replace("_", " "),
@@ -91,26 +91,45 @@ class HandoffHandler(FlagHandler):
agent = Agent(**agent_config)
agents.append(agent)
# Set up handoffs between agents
for i, agent in enumerate(agents[:-1]):
next_agent = agents[i + 1]
handoff = Handoff(
agent=next_agent,
tool_name_override=f"handoff_to_{agent_names[i + 1]}",
tool_description_override=f"Hand off task to {agent_names[i + 1]} for further processing"
)
# Second pass: Set up handoffs by recreating agents with handoff config
final_agents = []
for i, name in enumerate(agent_names):
agent_config = {
"name": name.title().replace("_", " "),
"role": name.title().replace("_", " "),
"goal": f"Complete tasks as {name}",
"backstory": f"Expert {name} with specialized skills"
}
# Add handoff to agent
if not hasattr(agent, 'handoffs') or agent.handoffs is None:
agent.handoffs = []
agent.handoffs.append(handoff)
if llm:
agent_config["llm"] = llm
# Add handoff to next agent if not the last agent
if i < len(agent_names) - 1:
next_agent = agents[i + 1]
next_agent_name = agent_names[i + 1]
# Pass handoffs via constructor - this properly registers them as tools
agent_config["handoffs"] = [
handoff(
agent=next_agent,
tool_name_override=f"handoff_to_{next_agent_name}",
tool_description_override=f"Hand off the task to {next_agent_name}. Call this after completing your part."
)
]
# Add instructions to use handoff
agent_config["instructions"] = f"After completing your task, you MUST use the handoff_to_{next_agent_name} tool to pass your work to the next agent."
final_agent = Agent(**agent_config)
final_agents.append(final_agent)
self.print_status(f"🤝 Created {len(agents)} agents with handoff chain", "success")
self.print_status(f"🤝 Created {len(final_agents)} agents with handoff chain", "success")
for i, name in enumerate(agent_names):
arrow = " → " if i < len(agent_names) - 1 else ""
self.print_status(f" {name}{arrow}", "info")
return agents
return final_agents
def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
"""
+257 -88
View File
@@ -1,32 +1,52 @@
"""
Image Handler for CLI.
Provides image processing capabilities using ImageAgent.
Usage: praisonai "Describe this image" --image path/to/image.png
Provides two distinct image capabilities:
1. Image Description (Vision) - Analyze existing images using vision-capable LLMs
Usage: praisonai "Describe this image" --image path/to/image.png
2. Image Generation - Generate new images from text prompts
Usage: praisonai "A sunset over mountains" --image-generate
"""
import os
from typing import Any, Dict, Tuple, List
import base64
import logging
from typing import Any, Dict, Tuple, List, Optional
from .base import FlagHandler
# Supported image extensions
SUPPORTED_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'}
logger = logging.getLogger(__name__)
# Supported image extensions for vision/description
SUPPORTED_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg', '.tiff'}
# Default vision-capable models (gpt-4o has better vision support than gpt-4o-mini)
DEFAULT_VISION_MODEL = "gpt-4o"
# Default image generation models
DEFAULT_IMAGE_GEN_MODEL = "dall-e-3"
SUPPORTED_IMAGE_GEN_MODELS = [
'gpt-image-1', 'gpt-image-1-mini', 'gpt-image-1.5',
'chatgpt-image-latest', 'dall-e-2', 'dall-e-3'
]
class ImageHandler(FlagHandler):
"""
Handler for --image flag.
Handler for --image flag (Image Description/Vision).
Processes images using ImageAgent for vision-based tasks.
Describes/analyzes existing images using vision-capable LLMs like GPT-4o.
This is for understanding what's IN an image, not generating new images.
Example:
praisonai "Describe this image" --image photo.png
praisonai "What's in this image?" --image /path/to/image.jpg
praisonai "Count the objects" --image image1.png,image2.png
"""
@property
def feature_name(self) -> str:
return "image"
return "image_describe"
@property
def flag_name(self) -> str:
@@ -34,17 +54,15 @@ class ImageHandler(FlagHandler):
@property
def flag_help(self) -> str:
return "Path to image file for vision-based tasks"
return "Path to image file for vision-based description/analysis"
def check_dependencies(self) -> Tuple[bool, str]:
"""Check if ImageAgent is available."""
"""Check if litellm is available for vision."""
try:
import importlib.util
if importlib.util.find_spec("praisonaiagents") is not None:
return True, ""
return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
import litellm
return True, ""
except ImportError:
return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
return False, "litellm not installed. Install with: pip install litellm"
def validate_image_path(self, path: str) -> Tuple[bool, str]:
"""
@@ -71,90 +89,96 @@ class ImageHandler(FlagHandler):
return True, ""
def _encode_image_to_base64(self, image_path: str) -> str:
"""Encode a local image file to base64."""
with open(image_path, 'rb') as f:
return base64.b64encode(f.read()).decode('utf-8')
def _get_image_mime_type(self, image_path: str) -> str:
"""Get MIME type based on file extension."""
ext = os.path.splitext(image_path)[1].lower()
mime_types = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.bmp': 'image/bmp',
'.svg': 'image/svg+xml',
'.tiff': 'image/tiff',
}
return mime_types.get(ext, 'image/png')
def _build_image_content(self, image_path: str) -> Dict[str, Any]:
"""Build image content for vision API."""
if image_path.startswith(('http://', 'https://')):
return {
"type": "image_url",
"image_url": {"url": image_path}
}
else:
mime_type = self._get_image_mime_type(image_path)
base64_data = self._encode_image_to_base64(image_path)
return {
"type": "image_url",
"image_url": {"url": f"data:{mime_type};base64,{base64_data}"}
}
def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
"""
Apply image configuration to agent config.
Args:
config: Agent configuration dictionary
flag_value: Image path or list of paths
Returns:
Modified configuration with image settings
"""
"""Apply image configuration to agent config."""
if flag_value:
# Store image path(s) for use with ImageAgent
if isinstance(flag_value, str):
config["images"] = [flag_value]
elif isinstance(flag_value, list):
config["images"] = flag_value
config["use_image_agent"] = True
config["use_vision"] = True
return config
def create_image_agent(self, llm: str = None, **kwargs) -> Any:
def describe_image(self, prompt: str, image_path: str, llm: str = None) -> str:
"""
Create an ImageAgent instance.
Args:
llm: Optional LLM model name
**kwargs: Additional agent configuration
Returns:
ImageAgent instance or None if unavailable
"""
available, msg = self.check_dependencies()
if not available:
self.print_status(msg, "error")
return None
from praisonaiagents import ImageAgent
agent_config = {
"name": "ImageAnalyzer",
"role": "Image Analyst",
"goal": "Analyze and describe images accurately",
"backstory": "Expert at understanding visual content"
}
if llm:
agent_config["llm"] = llm
agent_config.update(kwargs)
return ImageAgent(**agent_config)
def process_image(self, prompt: str, image_path: str, llm: str = None) -> str:
"""
Process an image with the given prompt.
Describe an image using vision-capable LLM.
Args:
prompt: The prompt/question about the image
image_path: Path to the image file
llm: Optional LLM model name
llm: Optional LLM model name (must be vision-capable)
Returns:
Agent's response about the image
Description of the image
"""
# Validate image path
valid, msg = self.validate_image_path(image_path)
if not valid:
return f"Error: {msg}"
agent = self.create_image_agent(llm=llm)
if not agent:
return "Error: Could not create ImageAgent"
available, dep_msg = self.check_dependencies()
if not available:
return f"Error: {dep_msg}"
import litellm
model = llm or DEFAULT_VISION_MODEL
try:
# ImageAgent expects images as a list
result = agent.chat(prompt, images=[image_path])
return result
# Build message with image
content = [
{"type": "text", "text": prompt},
self._build_image_content(image_path)
]
response = litellm.completion(
model=model,
messages=[{"role": "user", "content": content}]
)
return response.choices[0].message.content
except Exception as e:
self.log(f"Image processing error: {e}", "error")
return f"Error processing image: {e}"
logger.error(f"Error describing image: {e}")
return f"Error describing image: {e}"
def process_multiple_images(self, prompt: str, image_paths: List[str], llm: str = None) -> str:
def describe_multiple_images(self, prompt: str, image_paths: List[str], llm: str = None) -> str:
"""
Process multiple images with the given prompt.
Describe multiple images using vision-capable LLM.
Args:
prompt: The prompt/question about the images
@@ -162,36 +186,49 @@ class ImageHandler(FlagHandler):
llm: Optional LLM model name
Returns:
Agent's response about the images
Description of the images
"""
# Validate all image paths
for path in image_paths:
valid, msg = self.validate_image_path(path)
if not valid:
return f"Error: {msg}"
agent = self.create_image_agent(llm=llm)
if not agent:
return "Error: Could not create ImageAgent"
available, dep_msg = self.check_dependencies()
if not available:
return f"Error: {dep_msg}"
import litellm
model = llm or DEFAULT_VISION_MODEL
try:
result = agent.chat(prompt, images=image_paths)
return result
# Build message with all images
content = [{"type": "text", "text": prompt}]
for path in image_paths:
content.append(self._build_image_content(path))
response = litellm.completion(
model=model,
messages=[{"role": "user", "content": content}]
)
return response.choices[0].message.content
except Exception as e:
self.log(f"Image processing error: {e}", "error")
return f"Error processing images: {e}"
logger.error(f"Error describing images: {e}")
return f"Error describing images: {e}"
def execute(self, prompt: str = None, image_path: str = None, llm: str = None, **kwargs) -> str:
"""
Execute image processing.
Execute image description (vision).
Args:
prompt: The prompt/question about the image
image_path: Path to the image file (or comma-separated paths)
llm: Optional LLM model name
llm: Optional vision-capable LLM model name
Returns:
Agent's response
Description of the image(s)
"""
if not prompt:
return "Error: No prompt provided"
@@ -202,6 +239,138 @@ class ImageHandler(FlagHandler):
# Handle multiple images (comma-separated)
if ',' in image_path:
paths = [p.strip() for p in image_path.split(',')]
return self.process_multiple_images(prompt, paths, llm)
return self.describe_multiple_images(prompt, paths, llm)
return self.process_image(prompt, image_path, llm)
return self.describe_image(prompt, image_path, llm)
class ImageGenerateHandler(FlagHandler):
"""
Handler for --image-generate flag (Image Generation).
Generates NEW images from text prompts using DALL-E or similar models.
This is for CREATING images, not analyzing existing ones.
Example:
praisonai "A sunset over mountains" --image-generate
praisonai "A cat wearing a hat" --image-generate --llm dall-e-3
"""
@property
def feature_name(self) -> str:
return "image_generate"
@property
def flag_name(self) -> str:
return "image_generate"
@property
def flag_help(self) -> str:
return "Generate an image from the text prompt"
def check_dependencies(self) -> Tuple[bool, str]:
"""Check if ImageAgent is available."""
try:
import importlib.util
if importlib.util.find_spec("praisonaiagents") is not None:
return True, ""
return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
except ImportError:
return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
"""Apply image generation configuration."""
if flag_value:
config["use_image_generation"] = True
return config
def generate_image(self, prompt: str, llm: str = None, output_path: str = None) -> Dict[str, Any]:
"""
Generate an image from a text prompt.
Args:
prompt: Text description of the image to generate
llm: Image generation model (dall-e-3, dall-e-2, etc.)
output_path: Optional path to save the generated image
Returns:
Dict with image URL or path, or error message
"""
available, msg = self.check_dependencies()
if not available:
return {"error": msg}
from praisonaiagents import ImageAgent
# Use provided model or default
model = llm or DEFAULT_IMAGE_GEN_MODEL
# Validate model is supported for generation
if model not in SUPPORTED_IMAGE_GEN_MODELS:
return {
"error": f"Invalid model: '{model}'. Supported image generation models: {', '.join(SUPPORTED_IMAGE_GEN_MODELS)}"
}
try:
agent = ImageAgent(
name="ImageGenerator",
role="Image Generation Assistant",
goal="Generate high-quality images from text descriptions",
llm=model
)
result = agent.chat(prompt)
# Save image if output path provided
if output_path and isinstance(result, dict) and 'data' in result:
self._save_image(result, output_path)
result['saved_to'] = output_path
return result
except Exception as e:
logger.error(f"Error generating image: {e}")
return {"error": str(e)}
def _save_image(self, result: Dict[str, Any], output_path: str) -> bool:
"""Save generated image to file."""
try:
import requests
if 'data' in result and len(result['data']) > 0:
image_data = result['data'][0]
if 'url' in image_data:
# Download from URL
response = requests.get(image_data['url'])
with open(output_path, 'wb') as f:
f.write(response.content)
return True
elif 'b64_json' in image_data:
# Decode base64
import base64
img_bytes = base64.b64decode(image_data['b64_json'])
with open(output_path, 'wb') as f:
f.write(img_bytes)
return True
return False
except Exception as e:
logger.error(f"Error saving image: {e}")
return False
def execute(self, prompt: str = None, llm: str = None, output_path: str = None, **kwargs) -> Dict[str, Any]:
"""
Execute image generation.
Args:
prompt: Text description of the image to generate
llm: Image generation model name
output_path: Optional path to save the image
Returns:
Dict with generated image info or error
"""
if not prompt:
return {"error": "No prompt provided for image generation"}
return self.generate_image(prompt, llm, output_path)
@@ -107,6 +107,14 @@ class MetricsHandler(FlagHandler):
if hasattr(agent, 'last_token_metrics'):
metrics.update(agent.last_token_metrics or {})
# Check llm_model property for model info
if hasattr(agent, 'llm_model'):
llm_model = agent.llm_model
if hasattr(llm_model, 'model'):
metrics['model'] = llm_model.model
elif isinstance(llm_model, str):
metrics['model'] = llm_model
if hasattr(agent, 'llm') and agent.llm:
llm = agent.llm
if hasattr(llm, 'last_token_metrics'):
@@ -117,6 +125,14 @@ class MetricsHandler(FlagHandler):
if hasattr(agent, 'metrics') and isinstance(agent.metrics, dict):
metrics.update(agent.metrics or {})
# Try to get from litellm's global tracking
try:
import litellm
if hasattr(litellm, '_current_cost'):
metrics['cost'] = litellm._current_cost
except:
pass
return metrics
def post_process_result(self, result: Any, flag_value: Any) -> Any:
+28 -3
View File
@@ -733,8 +733,12 @@ class PraisonAI:
# Metrics - token/cost tracking
parser.add_argument("--metrics", action="store_true", help="Display token usage and cost metrics")
# Image - vision processing
parser.add_argument("--image", type=str, help="Path to image file for vision-based tasks")
# Image Description (Vision) - analyze existing images
parser.add_argument("--image", type=str, help="Path to image file for vision-based description/analysis")
# Image Generation - create new images from text
parser.add_argument("--image-generate", action="store_true", dest="image_generate",
help="Generate an image from the text prompt (use with --llm for model selection)")
# Telemetry - usage monitoring
parser.add_argument("--telemetry", action="store_true", help="Enable usage monitoring and analytics")
@@ -3291,7 +3295,7 @@ Provide ONLY the commit message, no explanations."""
return result
# Image processing - Use ImageAgent instead
# Image Description (Vision) - analyze existing images
if hasattr(self, 'args') and getattr(self.args, 'image', None):
from .features.image import ImageHandler
image_handler = ImageHandler(verbose=getattr(self.args, 'verbose', False))
@@ -3313,6 +3317,27 @@ Provide ONLY the commit message, no explanations."""
return result
# Image Generation - create new images from text
if hasattr(self, 'args') and getattr(self.args, 'image_generate', False):
from .features.image import ImageGenerateHandler
image_gen_handler = ImageGenerateHandler(verbose=getattr(self.args, 'verbose', False))
result = image_gen_handler.execute(
prompt=prompt,
llm=agent_config.get('llm')
)
# Format output for display
if isinstance(result, dict):
if 'error' in result:
print(f"[red]Error: {result['error']}[/red]")
elif 'data' in result and len(result['data']) > 0:
image_url = result['data'][0].get('url', result['data'][0].get('b64_json', 'Generated'))
print(f"[green]Image generated successfully![/green]")
if 'url' in result['data'][0]:
print(f"URL: {result['data'][0]['url']}")
return result
# Flow Display - Visual workflow tracking
if hasattr(self, 'args') and getattr(self.args, 'flow_display', False):
from .features.flow_display import FlowDisplayHandler
+1 -1
View File
@@ -57,7 +57,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.3.78 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.3.79 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.3.78"
__version__ = "2.3.79"