PraisonAI 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
praisonai/ui/chat.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
# Standard library imports
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
import logging
|
|
6
|
+
import json
|
|
7
|
+
import asyncio
|
|
8
|
+
import io
|
|
9
|
+
import base64
|
|
10
|
+
import importlib.util
|
|
11
|
+
import inspect
|
|
12
|
+
|
|
13
|
+
# Third-party imports
|
|
14
|
+
from dotenv import load_dotenv
|
|
15
|
+
from PIL import Image
|
|
16
|
+
from tavily import TavilyClient
|
|
17
|
+
from crawl4ai import AsyncWebCrawler
|
|
18
|
+
|
|
19
|
+
# Local application/library imports
|
|
20
|
+
import chainlit as cl
|
|
21
|
+
from chainlit.input_widget import TextInput
|
|
22
|
+
from chainlit.types import ThreadDict
|
|
23
|
+
import chainlit.data as cl_data
|
|
24
|
+
from litellm import acompletion
|
|
25
|
+
from literalai.helper import utc_now
|
|
26
|
+
from db import DatabaseManager
|
|
27
|
+
|
|
28
|
+
# Load environment variables
|
|
29
|
+
load_dotenv()
|
|
30
|
+
|
|
31
|
+
# Logging configuration
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
log_level = os.getenv("LOGLEVEL", "INFO").upper() or "INFO"
|
|
34
|
+
logger.handlers = []
|
|
35
|
+
console_handler = logging.StreamHandler()
|
|
36
|
+
console_handler.setLevel(log_level)
|
|
37
|
+
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
38
|
+
console_handler.setFormatter(console_formatter)
|
|
39
|
+
logger.addHandler(console_handler)
|
|
40
|
+
logger.setLevel(log_level)
|
|
41
|
+
|
|
42
|
+
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
|
|
43
|
+
if not CHAINLIT_AUTH_SECRET:
|
|
44
|
+
os.environ["CHAINLIT_AUTH_SECRET"] = "p8BPhQChpg@J>jBz$wGxqLX2V>yTVgP*7Ky9H$aV:axW~ANNX-7_T:o@lnyCBu^U"
|
|
45
|
+
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
|
|
46
|
+
|
|
47
|
+
now = utc_now()
|
|
48
|
+
create_step_counter = 0
|
|
49
|
+
|
|
50
|
+
# Initialize database
|
|
51
|
+
db_manager = DatabaseManager()
|
|
52
|
+
db_manager.initialize()
|
|
53
|
+
|
|
54
|
+
def save_setting(key: str, value: str):
|
|
55
|
+
"""Save a setting to the database"""
|
|
56
|
+
asyncio.run(db_manager.save_setting(key, value))
|
|
57
|
+
|
|
58
|
+
def load_setting(key: str) -> str:
|
|
59
|
+
"""Load a setting from the database"""
|
|
60
|
+
return asyncio.run(db_manager.load_setting(key))
|
|
61
|
+
|
|
62
|
+
cl_data._data_layer = db_manager
|
|
63
|
+
|
|
64
|
+
def load_custom_tools():
|
|
65
|
+
"""
|
|
66
|
+
Load custom tools from tools.py if it exists.
|
|
67
|
+
|
|
68
|
+
Resolution order:
|
|
69
|
+
1. PRAISONAI_TOOLS_PATH environment variable (file or directory)
|
|
70
|
+
2. ./tools.py in current working directory
|
|
71
|
+
3. No tools (silent, no warning)
|
|
72
|
+
"""
|
|
73
|
+
custom_tools = {}
|
|
74
|
+
|
|
75
|
+
# Determine tools path
|
|
76
|
+
tools_path = os.getenv("PRAISONAI_TOOLS_PATH")
|
|
77
|
+
if tools_path:
|
|
78
|
+
if not os.path.exists(tools_path):
|
|
79
|
+
logger.warning(f"PRAISONAI_TOOLS_PATH set but path does not exist: {tools_path}")
|
|
80
|
+
return custom_tools
|
|
81
|
+
else:
|
|
82
|
+
# Check current working directory for tools.py
|
|
83
|
+
cwd_tools = os.path.join(os.getcwd(), "tools.py")
|
|
84
|
+
if os.path.exists(cwd_tools):
|
|
85
|
+
tools_path = cwd_tools
|
|
86
|
+
else:
|
|
87
|
+
# No tools configured - this is fine, return silently
|
|
88
|
+
logger.debug("No tools.py found in current directory (this is normal)")
|
|
89
|
+
return custom_tools
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
spec = importlib.util.spec_from_file_location("tools", tools_path)
|
|
93
|
+
if spec is None or spec.loader is None:
|
|
94
|
+
logger.debug(f"Could not load tools from {tools_path}")
|
|
95
|
+
return custom_tools
|
|
96
|
+
|
|
97
|
+
module = importlib.util.module_from_spec(spec)
|
|
98
|
+
spec.loader.exec_module(module)
|
|
99
|
+
|
|
100
|
+
# Load all functions from tools.py
|
|
101
|
+
for name, obj in inspect.getmembers(module):
|
|
102
|
+
if not name.startswith('_') and callable(obj) and not inspect.isclass(obj):
|
|
103
|
+
# Store function in globals for access
|
|
104
|
+
globals()[name] = obj
|
|
105
|
+
|
|
106
|
+
# Get function signature to build parameters
|
|
107
|
+
sig = inspect.signature(obj)
|
|
108
|
+
params_properties = {}
|
|
109
|
+
required_params = []
|
|
110
|
+
|
|
111
|
+
for param_name, param in sig.parameters.items():
|
|
112
|
+
if param_name != 'self': # Skip self parameter
|
|
113
|
+
# Get type annotation if available
|
|
114
|
+
param_type = "string" # Default type
|
|
115
|
+
if param.annotation != inspect.Parameter.empty:
|
|
116
|
+
if param.annotation == int:
|
|
117
|
+
param_type = "integer"
|
|
118
|
+
elif param.annotation == float:
|
|
119
|
+
param_type = "number"
|
|
120
|
+
elif param.annotation == bool:
|
|
121
|
+
param_type = "boolean"
|
|
122
|
+
|
|
123
|
+
params_properties[param_name] = {
|
|
124
|
+
"type": param_type,
|
|
125
|
+
"description": f"Parameter {param_name}"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Add to required if no default value
|
|
129
|
+
if param.default == inspect.Parameter.empty:
|
|
130
|
+
required_params.append(param_name)
|
|
131
|
+
|
|
132
|
+
# Build tool definition
|
|
133
|
+
tool_def = {
|
|
134
|
+
"type": "function",
|
|
135
|
+
"function": {
|
|
136
|
+
"name": name,
|
|
137
|
+
"description": obj.__doc__ or f"Function {name.replace('_', ' ')}",
|
|
138
|
+
"parameters": {
|
|
139
|
+
"type": "object",
|
|
140
|
+
"properties": params_properties,
|
|
141
|
+
"required": required_params
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
custom_tools[name] = tool_def
|
|
147
|
+
logger.info(f"Loaded custom tool: {name}")
|
|
148
|
+
|
|
149
|
+
if custom_tools:
|
|
150
|
+
logger.info(f"Loaded {len(custom_tools)} custom tools from {tools_path}")
|
|
151
|
+
except FileNotFoundError:
|
|
152
|
+
# File doesn't exist - this is fine, no warning needed
|
|
153
|
+
logger.debug(f"Tools file not found: {tools_path}")
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.warning(f"Error loading custom tools from {tools_path}: {e}")
|
|
156
|
+
|
|
157
|
+
return custom_tools
|
|
158
|
+
|
|
159
|
+
# Load custom tools
|
|
160
|
+
custom_tools_dict = load_custom_tools()
|
|
161
|
+
|
|
162
|
+
tavily_api_key = os.getenv("TAVILY_API_KEY")
|
|
163
|
+
tavily_client = TavilyClient(api_key=tavily_api_key) if tavily_api_key else None
|
|
164
|
+
|
|
165
|
+
async def tavily_web_search(query):
|
|
166
|
+
if not tavily_client:
|
|
167
|
+
return json.dumps({
|
|
168
|
+
"query": query,
|
|
169
|
+
"error": "Tavily API key is not set. Web search is unavailable."
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
response = tavily_client.search(query)
|
|
173
|
+
logger.debug(f"Tavily search response: {response}")
|
|
174
|
+
|
|
175
|
+
async with AsyncWebCrawler() as crawler:
|
|
176
|
+
results = []
|
|
177
|
+
for result in response.get('results', []):
|
|
178
|
+
url = result.get('url')
|
|
179
|
+
if url:
|
|
180
|
+
try:
|
|
181
|
+
crawl_result = await crawler.arun(url=url)
|
|
182
|
+
results.append({
|
|
183
|
+
"content": result.get('content'),
|
|
184
|
+
"url": url,
|
|
185
|
+
"full_content": crawl_result.markdown
|
|
186
|
+
})
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.error(f"Error crawling {url}: {str(e)}")
|
|
189
|
+
results.append({
|
|
190
|
+
"content": result.get('content'),
|
|
191
|
+
"url": url,
|
|
192
|
+
"full_content": "Error: Unable to crawl this URL"
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
return json.dumps({
|
|
196
|
+
"query": query,
|
|
197
|
+
"results": results
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
# Build tools list with Tavily and custom tools
|
|
201
|
+
tools = []
|
|
202
|
+
|
|
203
|
+
# Add Tavily tool if API key is available
|
|
204
|
+
if tavily_api_key:
|
|
205
|
+
tools.append({
|
|
206
|
+
"type": "function",
|
|
207
|
+
"function": {
|
|
208
|
+
"name": "tavily_web_search",
|
|
209
|
+
"description": "Search the web using Tavily API and crawl the resulting URLs",
|
|
210
|
+
"parameters": {
|
|
211
|
+
"type": "object",
|
|
212
|
+
"properties": {
|
|
213
|
+
"query": {"type": "string", "description": "Search query"}
|
|
214
|
+
},
|
|
215
|
+
"required": ["query"]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
# Add custom tools from tools.py
|
|
221
|
+
tools.extend(list(custom_tools_dict.values()))
|
|
222
|
+
|
|
223
|
+
# Authentication configuration
|
|
224
|
+
AUTH_PASSWORD_ENABLED = os.getenv("AUTH_PASSWORD_ENABLED", "true").lower() == "true" # Password authentication enabled by default
|
|
225
|
+
AUTH_OAUTH_ENABLED = os.getenv("AUTH_OAUTH_ENABLED", "false").lower() == "true" # OAuth authentication disabled by default
|
|
226
|
+
|
|
227
|
+
expected_username = os.getenv("CHAINLIT_USERNAME", "admin")
|
|
228
|
+
expected_password = os.getenv("CHAINLIT_PASSWORD", "admin")
|
|
229
|
+
|
|
230
|
+
# Warn if using default credentials
|
|
231
|
+
if expected_username == "admin" and expected_password == "admin":
|
|
232
|
+
logger.warning("⚠️ Using default admin credentials. Set CHAINLIT_USERNAME and CHAINLIT_PASSWORD environment variables for production.")
|
|
233
|
+
|
|
234
|
+
@cl.password_auth_callback
|
|
235
|
+
def auth_callback(username: str, password: str):
|
|
236
|
+
logger.debug(f"Auth attempt: username='{username}', expected='{expected_username}'")
|
|
237
|
+
if (username, password) == (expected_username, expected_password):
|
|
238
|
+
logger.info(f"Login successful for user: {username}")
|
|
239
|
+
return cl.User(identifier=username, metadata={"role": "admin", "provider": "credentials"})
|
|
240
|
+
else:
|
|
241
|
+
logger.warning(f"Login failed for user: {username}")
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
async def send_count():
|
|
246
|
+
await cl.Message(
|
|
247
|
+
f"Create step counter: {create_step_counter}", disable_feedback=True
|
|
248
|
+
).send()
|
|
249
|
+
|
|
250
|
+
@cl.on_chat_start
|
|
251
|
+
async def start():
|
|
252
|
+
model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-5-nano")
|
|
253
|
+
cl.user_session.set("model_name", model_name)
|
|
254
|
+
logger.debug(f"Model name: {model_name}")
|
|
255
|
+
settings = cl.ChatSettings(
|
|
256
|
+
[
|
|
257
|
+
TextInput(
|
|
258
|
+
id="model_name",
|
|
259
|
+
label="Enter the Model Name",
|
|
260
|
+
placeholder="e.g., gpt-5-nano",
|
|
261
|
+
initial=model_name
|
|
262
|
+
)
|
|
263
|
+
]
|
|
264
|
+
)
|
|
265
|
+
cl.user_session.set("settings", settings)
|
|
266
|
+
await settings.send()
|
|
267
|
+
|
|
268
|
+
@cl.on_settings_update
|
|
269
|
+
async def setup_agent(settings):
|
|
270
|
+
logger.debug(settings)
|
|
271
|
+
cl.user_session.set("settings", settings)
|
|
272
|
+
model_name = settings["model_name"]
|
|
273
|
+
cl.user_session.set("model_name", model_name)
|
|
274
|
+
|
|
275
|
+
save_setting("model_name", model_name)
|
|
276
|
+
|
|
277
|
+
thread_id = cl.user_session.get("thread_id")
|
|
278
|
+
if thread_id:
|
|
279
|
+
thread = await cl_data.get_thread(thread_id)
|
|
280
|
+
if thread:
|
|
281
|
+
metadata = thread.get("metadata", {})
|
|
282
|
+
if isinstance(metadata, str):
|
|
283
|
+
try:
|
|
284
|
+
metadata = json.loads(metadata)
|
|
285
|
+
except json.JSONDecodeError:
|
|
286
|
+
metadata = {}
|
|
287
|
+
metadata["model_name"] = model_name
|
|
288
|
+
await cl_data.update_thread(thread_id, metadata=metadata)
|
|
289
|
+
cl.user_session.set("metadata", metadata)
|
|
290
|
+
|
|
291
|
+
@cl.on_message
|
|
292
|
+
async def main(message: cl.Message):
|
|
293
|
+
model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-5-nano")
|
|
294
|
+
message_history = cl.user_session.get("message_history", [])
|
|
295
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
296
|
+
|
|
297
|
+
image = None
|
|
298
|
+
if message.elements and isinstance(message.elements[0], cl.Image):
|
|
299
|
+
image_element = message.elements[0]
|
|
300
|
+
try:
|
|
301
|
+
image = Image.open(image_element.path)
|
|
302
|
+
image.load()
|
|
303
|
+
cl.user_session.set("image", image)
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.error(f"Error processing image: {str(e)}")
|
|
306
|
+
await cl.Message(content="Error processing the image. Please try again.").send()
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
user_message = f"""
|
|
310
|
+
Answer the question and use tools if needed:
|
|
311
|
+
|
|
312
|
+
Current Date and Time: {now}
|
|
313
|
+
|
|
314
|
+
User Question: {message.content}
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
if image:
|
|
318
|
+
user_message = f"Image uploaded. {user_message}"
|
|
319
|
+
|
|
320
|
+
message_history.append({"role": "user", "content": user_message})
|
|
321
|
+
msg = cl.Message(content="")
|
|
322
|
+
|
|
323
|
+
completion_params = {
|
|
324
|
+
"model": model_name,
|
|
325
|
+
"messages": message_history,
|
|
326
|
+
"stream": True,
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if image:
|
|
330
|
+
buffered = io.BytesIO()
|
|
331
|
+
image.save(buffered, format="PNG")
|
|
332
|
+
img_str = base64.b64encode(buffered.getvalue()).decode()
|
|
333
|
+
completion_params["messages"][-1] = {
|
|
334
|
+
"role": "user",
|
|
335
|
+
"content": [
|
|
336
|
+
{"type": "text", "text": user_message},
|
|
337
|
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_str}"}}
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
# Pass tools if we have any (Tavily or custom)
|
|
342
|
+
if tools:
|
|
343
|
+
completion_params["tools"] = tools
|
|
344
|
+
completion_params["tool_choice"] = "auto"
|
|
345
|
+
|
|
346
|
+
response = await acompletion(**completion_params)
|
|
347
|
+
|
|
348
|
+
full_response = ""
|
|
349
|
+
tool_calls = []
|
|
350
|
+
current_tool_call = None
|
|
351
|
+
msg_sent = False
|
|
352
|
+
|
|
353
|
+
async for part in response:
|
|
354
|
+
if 'choices' in part and len(part['choices']) > 0:
|
|
355
|
+
delta = part['choices'][0].get('delta', {})
|
|
356
|
+
|
|
357
|
+
if 'content' in delta and delta['content'] is not None:
|
|
358
|
+
token = delta['content']
|
|
359
|
+
# Send message on first token to avoid delay
|
|
360
|
+
if not msg_sent:
|
|
361
|
+
await msg.send()
|
|
362
|
+
msg_sent = True
|
|
363
|
+
await msg.stream_token(token)
|
|
364
|
+
full_response += token
|
|
365
|
+
|
|
366
|
+
if tools and 'tool_calls' in delta and delta['tool_calls'] is not None:
|
|
367
|
+
for tool_call in delta['tool_calls']:
|
|
368
|
+
if current_tool_call is None or tool_call.index != current_tool_call['index']:
|
|
369
|
+
if current_tool_call:
|
|
370
|
+
tool_calls.append(current_tool_call)
|
|
371
|
+
current_tool_call = {
|
|
372
|
+
'id': tool_call.id,
|
|
373
|
+
'type': tool_call.type,
|
|
374
|
+
'index': tool_call.index,
|
|
375
|
+
'function': {
|
|
376
|
+
'name': tool_call.function.name if tool_call.function else None,
|
|
377
|
+
'arguments': ''
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if tool_call.function:
|
|
381
|
+
if tool_call.function.name:
|
|
382
|
+
current_tool_call['function']['name'] = tool_call.function.name
|
|
383
|
+
if tool_call.function.arguments:
|
|
384
|
+
current_tool_call['function']['arguments'] += tool_call.function.arguments
|
|
385
|
+
|
|
386
|
+
if current_tool_call:
|
|
387
|
+
tool_calls.append(current_tool_call)
|
|
388
|
+
|
|
389
|
+
# Ensure message is sent even if no content (e.g., tool calls only)
|
|
390
|
+
if not msg_sent:
|
|
391
|
+
await msg.send()
|
|
392
|
+
|
|
393
|
+
logger.debug(f"Full response: {full_response}")
|
|
394
|
+
logger.debug(f"Tool calls: {tool_calls}")
|
|
395
|
+
message_history.append({"role": "assistant", "content": full_response})
|
|
396
|
+
logger.debug(f"Message history: {message_history}")
|
|
397
|
+
cl.user_session.set("message_history", message_history)
|
|
398
|
+
await msg.update()
|
|
399
|
+
|
|
400
|
+
if tool_calls and tools: # Check if we have any tools and tool calls
|
|
401
|
+
available_functions = {}
|
|
402
|
+
|
|
403
|
+
# Add Tavily function if available
|
|
404
|
+
if tavily_api_key:
|
|
405
|
+
available_functions["tavily_web_search"] = tavily_web_search
|
|
406
|
+
|
|
407
|
+
# Add all custom tool functions from globals
|
|
408
|
+
for tool_name in custom_tools_dict:
|
|
409
|
+
if tool_name in globals():
|
|
410
|
+
available_functions[tool_name] = globals()[tool_name]
|
|
411
|
+
messages = message_history + [{"role": "assistant", "content": None, "function_call": {
|
|
412
|
+
"name": tool_calls[0]['function']['name'],
|
|
413
|
+
"arguments": tool_calls[0]['function']['arguments']
|
|
414
|
+
}}]
|
|
415
|
+
|
|
416
|
+
for tool_call in tool_calls:
|
|
417
|
+
function_name = tool_call['function']['name']
|
|
418
|
+
if function_name in available_functions:
|
|
419
|
+
function_to_call = available_functions[function_name]
|
|
420
|
+
function_args = tool_call['function']['arguments']
|
|
421
|
+
if function_args:
|
|
422
|
+
try:
|
|
423
|
+
function_args = json.loads(function_args)
|
|
424
|
+
|
|
425
|
+
# Call function based on whether it's async or sync
|
|
426
|
+
if asyncio.iscoroutinefunction(function_to_call):
|
|
427
|
+
# For async functions like tavily_web_search
|
|
428
|
+
if function_name == "tavily_web_search":
|
|
429
|
+
function_response = await function_to_call(
|
|
430
|
+
query=function_args.get("query"),
|
|
431
|
+
)
|
|
432
|
+
else:
|
|
433
|
+
# For custom async functions, pass all arguments
|
|
434
|
+
function_response = await function_to_call(**function_args)
|
|
435
|
+
else:
|
|
436
|
+
# For sync functions (most custom tools)
|
|
437
|
+
function_response = function_to_call(**function_args)
|
|
438
|
+
|
|
439
|
+
# Convert response to string if needed
|
|
440
|
+
if not isinstance(function_response, str):
|
|
441
|
+
function_response = json.dumps(function_response)
|
|
442
|
+
|
|
443
|
+
messages.append(
|
|
444
|
+
{
|
|
445
|
+
"role": "function",
|
|
446
|
+
"name": function_name,
|
|
447
|
+
"content": function_response,
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
except json.JSONDecodeError:
|
|
451
|
+
logger.error(f"Failed to parse function arguments: {function_args}")
|
|
452
|
+
except Exception as e:
|
|
453
|
+
logger.error(f"Error calling function {function_name}: {str(e)}")
|
|
454
|
+
messages.append(
|
|
455
|
+
{
|
|
456
|
+
"role": "function",
|
|
457
|
+
"name": function_name,
|
|
458
|
+
"content": f"Error: {str(e)}",
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
second_response = await acompletion(
|
|
463
|
+
model=model_name,
|
|
464
|
+
stream=True,
|
|
465
|
+
messages=messages,
|
|
466
|
+
)
|
|
467
|
+
logger.debug(f"Second LLM response: {second_response}")
|
|
468
|
+
|
|
469
|
+
full_response = ""
|
|
470
|
+
async for part in second_response:
|
|
471
|
+
if 'choices' in part and len(part['choices']) > 0:
|
|
472
|
+
delta = part['choices'][0].get('delta', {})
|
|
473
|
+
if 'content' in delta and delta['content'] is not None:
|
|
474
|
+
token = delta['content']
|
|
475
|
+
await msg.stream_token(token)
|
|
476
|
+
full_response += token
|
|
477
|
+
|
|
478
|
+
msg.content = full_response
|
|
479
|
+
await msg.update()
|
|
480
|
+
else:
|
|
481
|
+
msg.content = full_response
|
|
482
|
+
await msg.update()
|
|
483
|
+
|
|
484
|
+
@cl.on_chat_resume
|
|
485
|
+
async def on_chat_resume(thread: ThreadDict):
|
|
486
|
+
logger.info(f"Resuming chat: {thread['id']}")
|
|
487
|
+
model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-5-nano")
|
|
488
|
+
logger.debug(f"Model name: {model_name}")
|
|
489
|
+
settings = cl.ChatSettings(
|
|
490
|
+
[
|
|
491
|
+
TextInput(
|
|
492
|
+
id="model_name",
|
|
493
|
+
label="Enter the Model Name",
|
|
494
|
+
placeholder="e.g., gpt-5-nano",
|
|
495
|
+
initial=model_name
|
|
496
|
+
)
|
|
497
|
+
]
|
|
498
|
+
)
|
|
499
|
+
await settings.send()
|
|
500
|
+
thread_id = thread["id"]
|
|
501
|
+
cl.user_session.set("thread_id", thread_id)
|
|
502
|
+
|
|
503
|
+
metadata = thread.get("metadata", {})
|
|
504
|
+
if isinstance(metadata, str):
|
|
505
|
+
try:
|
|
506
|
+
metadata = json.loads(metadata)
|
|
507
|
+
except json.JSONDecodeError:
|
|
508
|
+
metadata = {}
|
|
509
|
+
cl.user_session.set("metadata", metadata)
|
|
510
|
+
|
|
511
|
+
message_history = cl.user_session.get("message_history", [])
|
|
512
|
+
steps = thread["steps"]
|
|
513
|
+
|
|
514
|
+
for m in steps:
|
|
515
|
+
msg_type = m.get("type")
|
|
516
|
+
if msg_type == "user_message":
|
|
517
|
+
message_history.append({"role": "user", "content": m.get("output", "")})
|
|
518
|
+
elif msg_type == "assistant_message":
|
|
519
|
+
message_history.append({"role": "assistant", "content": m.get("output", "")})
|
|
520
|
+
elif msg_type == "run":
|
|
521
|
+
if m.get("isError"):
|
|
522
|
+
message_history.append({"role": "system", "content": f"Error: {m.get('output', '')}"})
|
|
523
|
+
else:
|
|
524
|
+
logger.warning(f"Message without recognized type: {m}")
|
|
525
|
+
|
|
526
|
+
cl.user_session.set("message_history", message_history)
|
|
527
|
+
|
|
528
|
+
image_data = metadata.get("image")
|
|
529
|
+
if image_data:
|
|
530
|
+
image = Image.open(io.BytesIO(base64.b64decode(image_data)))
|
|
531
|
+
cl.user_session.set("image", image)
|
|
532
|
+
await cl.Message(content="Previous image loaded.").send()
|