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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main Screen for PraisonAI TUI.
|
|
3
|
+
|
|
4
|
+
The primary chat interface screen.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from textual.screen import Screen
|
|
11
|
+
from textual.containers import Horizontal, Vertical, Container
|
|
12
|
+
from textual.widgets import Static, Footer, Header
|
|
13
|
+
from textual.binding import Binding
|
|
14
|
+
from textual.message import Message
|
|
15
|
+
TEXTUAL_AVAILABLE = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
TEXTUAL_AVAILABLE = False
|
|
18
|
+
Screen = object
|
|
19
|
+
Message = object
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TEXTUAL_AVAILABLE:
|
|
23
|
+
from ..widgets.chat import ChatWidget, ChatMessage
|
|
24
|
+
from ..widgets.composer import ComposerWidget
|
|
25
|
+
from ..widgets.status import StatusWidget, StatusInfo
|
|
26
|
+
from ..widgets.queue_panel import QueuePanelWidget
|
|
27
|
+
from ..widgets.tool_panel import ToolPanelWidget
|
|
28
|
+
|
|
29
|
+
class MainScreen(Screen):
|
|
30
|
+
"""
|
|
31
|
+
Main chat screen.
|
|
32
|
+
|
|
33
|
+
Layout:
|
|
34
|
+
- Status bar (top)
|
|
35
|
+
- Chat history (main area)
|
|
36
|
+
- Tools panel (right sidebar)
|
|
37
|
+
- Queue panel (right sidebar)
|
|
38
|
+
- Composer (bottom)
|
|
39
|
+
- Footer with keybindings
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
BINDINGS = [
|
|
43
|
+
Binding("ctrl+q", "quit", "Quit", show=True),
|
|
44
|
+
Binding("ctrl+c", "cancel", "Cancel", show=True),
|
|
45
|
+
Binding("ctrl+l", "clear", "Clear", show=True),
|
|
46
|
+
Binding("f1", "help", "Help", show=True),
|
|
47
|
+
Binding("f2", "toggle_queue", "Queue", show=True),
|
|
48
|
+
Binding("f3", "settings", "Settings", show=True),
|
|
49
|
+
Binding("f5", "clear_chat", "Clear Chat", show=True),
|
|
50
|
+
Binding("tab", "focus_next", "Next", show=False),
|
|
51
|
+
Binding("escape", "cancel_input", "Cancel", show=False),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
DEFAULT_CSS = """
|
|
55
|
+
MainScreen {
|
|
56
|
+
layout: grid;
|
|
57
|
+
grid-size: 2;
|
|
58
|
+
grid-columns: 3fr 1fr;
|
|
59
|
+
grid-rows: 1 1fr auto 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
MainScreen #status-bar {
|
|
63
|
+
column-span: 2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
MainScreen #main-area {
|
|
67
|
+
height: 100%;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
MainScreen #sidebar {
|
|
71
|
+
height: 100%;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
MainScreen #composer-area {
|
|
75
|
+
column-span: 2;
|
|
76
|
+
height: auto;
|
|
77
|
+
max-height: 10;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
MainScreen Footer {
|
|
81
|
+
column-span: 2;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
MainScreen .sidebar-panel {
|
|
85
|
+
height: 1fr;
|
|
86
|
+
}
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
class MessageSubmitted(Message):
|
|
90
|
+
"""Event when a message is submitted."""
|
|
91
|
+
def __init__(self, content: str):
|
|
92
|
+
self.content = content
|
|
93
|
+
super().__init__()
|
|
94
|
+
|
|
95
|
+
class CommandExecuted(Message):
|
|
96
|
+
"""Event when a command is executed."""
|
|
97
|
+
def __init__(self, command: str, args: str):
|
|
98
|
+
self.command = command
|
|
99
|
+
self.args = args
|
|
100
|
+
super().__init__()
|
|
101
|
+
|
|
102
|
+
class CancelRequested(Message):
|
|
103
|
+
"""Event when cancel is requested."""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
show_queue: bool = True,
|
|
109
|
+
show_tools: bool = True,
|
|
110
|
+
name: Optional[str] = None,
|
|
111
|
+
id: Optional[str] = None,
|
|
112
|
+
classes: Optional[str] = None,
|
|
113
|
+
):
|
|
114
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
115
|
+
self._show_queue = show_queue
|
|
116
|
+
self._show_tools = show_tools
|
|
117
|
+
|
|
118
|
+
def compose(self):
|
|
119
|
+
"""Compose the screen."""
|
|
120
|
+
yield StatusWidget(id="status-bar")
|
|
121
|
+
|
|
122
|
+
yield ChatWidget(id="chat-widget")
|
|
123
|
+
|
|
124
|
+
with Vertical(id="sidebar"):
|
|
125
|
+
if self._show_tools:
|
|
126
|
+
yield ToolPanelWidget(id="tool-panel", classes="sidebar-panel")
|
|
127
|
+
if self._show_queue:
|
|
128
|
+
yield QueuePanelWidget(id="queue-panel", classes="sidebar-panel")
|
|
129
|
+
|
|
130
|
+
yield ComposerWidget(id="composer")
|
|
131
|
+
|
|
132
|
+
yield Footer()
|
|
133
|
+
|
|
134
|
+
def on_mount(self) -> None:
|
|
135
|
+
"""Handle mount."""
|
|
136
|
+
# Focus composer
|
|
137
|
+
composer = self.query_one("#composer", ComposerWidget)
|
|
138
|
+
composer.focus_input()
|
|
139
|
+
|
|
140
|
+
def on_composer_widget_submitted(self, event: ComposerWidget.Submitted) -> None:
|
|
141
|
+
"""Handle message submission."""
|
|
142
|
+
if event.is_command:
|
|
143
|
+
# Parse command
|
|
144
|
+
parts = event.content[1:].split(maxsplit=1)
|
|
145
|
+
command = parts[0] if parts else ""
|
|
146
|
+
args = parts[1] if len(parts) > 1 else ""
|
|
147
|
+
self.post_message(self.CommandExecuted(command, args))
|
|
148
|
+
else:
|
|
149
|
+
self.post_message(self.MessageSubmitted(event.content))
|
|
150
|
+
|
|
151
|
+
def action_quit(self) -> None:
|
|
152
|
+
"""Quit the application."""
|
|
153
|
+
self.app.exit()
|
|
154
|
+
|
|
155
|
+
def action_cancel(self) -> None:
|
|
156
|
+
"""Cancel current operation."""
|
|
157
|
+
self.post_message(self.CancelRequested())
|
|
158
|
+
|
|
159
|
+
def action_clear(self) -> None:
|
|
160
|
+
"""Clear screen."""
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
def action_help(self) -> None:
|
|
164
|
+
"""Show help."""
|
|
165
|
+
self.app.push_screen("help")
|
|
166
|
+
|
|
167
|
+
def action_toggle_queue(self) -> None:
|
|
168
|
+
"""Toggle queue panel visibility."""
|
|
169
|
+
try:
|
|
170
|
+
queue_panel = self.query_one("#queue-panel", QueuePanelWidget)
|
|
171
|
+
queue_panel.display = not queue_panel.display
|
|
172
|
+
except Exception:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
def action_settings(self) -> None:
|
|
176
|
+
"""Show settings."""
|
|
177
|
+
self.app.push_screen("settings")
|
|
178
|
+
|
|
179
|
+
def action_clear_chat(self) -> None:
|
|
180
|
+
"""Clear chat history."""
|
|
181
|
+
chat = self.query_one("#chat-widget", ChatWidget)
|
|
182
|
+
self.app.call_later(chat.clear)
|
|
183
|
+
|
|
184
|
+
def action_cancel_input(self) -> None:
|
|
185
|
+
"""Cancel current input."""
|
|
186
|
+
composer = self.query_one("#composer", ComposerWidget)
|
|
187
|
+
composer.action_cancel()
|
|
188
|
+
|
|
189
|
+
# Public methods for updating UI
|
|
190
|
+
|
|
191
|
+
async def add_user_message(self, content: str) -> None:
|
|
192
|
+
"""Add a user message to chat."""
|
|
193
|
+
chat = self.query_one("#chat-widget", ChatWidget)
|
|
194
|
+
await chat.add_message(ChatMessage(
|
|
195
|
+
role="user",
|
|
196
|
+
content=content,
|
|
197
|
+
))
|
|
198
|
+
|
|
199
|
+
async def add_assistant_message(
|
|
200
|
+
self,
|
|
201
|
+
content: str,
|
|
202
|
+
run_id: Optional[str] = None,
|
|
203
|
+
agent_name: Optional[str] = None,
|
|
204
|
+
streaming: bool = False,
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Add an assistant message to chat."""
|
|
207
|
+
chat = self.query_one("#chat-widget", ChatWidget)
|
|
208
|
+
await chat.add_message(ChatMessage(
|
|
209
|
+
role="assistant",
|
|
210
|
+
content=content,
|
|
211
|
+
run_id=run_id,
|
|
212
|
+
agent_name=agent_name,
|
|
213
|
+
is_streaming=streaming,
|
|
214
|
+
))
|
|
215
|
+
|
|
216
|
+
async def update_streaming(self, run_id: str, content: str) -> None:
|
|
217
|
+
"""Update streaming message."""
|
|
218
|
+
chat = self.query_one("#chat-widget", ChatWidget)
|
|
219
|
+
await chat.update_streaming(run_id, content)
|
|
220
|
+
|
|
221
|
+
async def complete_streaming(self, run_id: str, content: str) -> None:
|
|
222
|
+
"""Complete streaming message."""
|
|
223
|
+
chat = self.query_one("#chat-widget", ChatWidget)
|
|
224
|
+
await chat.complete_streaming(run_id, content)
|
|
225
|
+
|
|
226
|
+
def update_status(self, info: StatusInfo) -> None:
|
|
227
|
+
"""Update status bar."""
|
|
228
|
+
status = self.query_one("#status-bar", StatusWidget)
|
|
229
|
+
status.update_info(info)
|
|
230
|
+
|
|
231
|
+
def set_processing(self, processing: bool) -> None:
|
|
232
|
+
"""Set processing state."""
|
|
233
|
+
composer = self.query_one("#composer", ComposerWidget)
|
|
234
|
+
composer.set_processing(processing)
|
|
235
|
+
|
|
236
|
+
status = self.query_one("#status-bar", StatusWidget)
|
|
237
|
+
status.is_processing = processing
|
|
238
|
+
|
|
239
|
+
else:
|
|
240
|
+
class MainScreen:
|
|
241
|
+
"""Placeholder when Textual is not available."""
|
|
242
|
+
def __init__(self, *args, **kwargs):
|
|
243
|
+
raise ImportError(
|
|
244
|
+
"Textual is required for TUI. Install with: pip install praisonai[tui]"
|
|
245
|
+
)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Queue Screen for PraisonAI TUI.
|
|
3
|
+
|
|
4
|
+
Queue management interface.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional, List
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from textual.screen import Screen
|
|
11
|
+
from textual.containers import Vertical, Horizontal
|
|
12
|
+
from textual.widgets import Static, Footer, DataTable, Button
|
|
13
|
+
from textual.binding import Binding
|
|
14
|
+
from textual.message import Message
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
TEXTUAL_AVAILABLE = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
TEXTUAL_AVAILABLE = False
|
|
19
|
+
Screen = object
|
|
20
|
+
Message = object
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TEXTUAL_AVAILABLE:
|
|
24
|
+
class QueueScreen(Screen):
|
|
25
|
+
"""
|
|
26
|
+
Queue management screen.
|
|
27
|
+
|
|
28
|
+
Displays full queue with management options.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
BINDINGS = [
|
|
32
|
+
Binding("escape", "back", "Back", show=True),
|
|
33
|
+
Binding("c", "cancel", "Cancel", show=True),
|
|
34
|
+
Binding("r", "retry", "Retry", show=True),
|
|
35
|
+
Binding("x", "clear", "Clear Queue", show=True),
|
|
36
|
+
Binding("enter", "select", "Select", show=False),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
DEFAULT_CSS = """
|
|
40
|
+
QueueScreen {
|
|
41
|
+
background: $surface;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
QueueScreen #queue-header {
|
|
45
|
+
height: 3;
|
|
46
|
+
background: $primary;
|
|
47
|
+
padding: 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
QueueScreen DataTable {
|
|
51
|
+
height: 1fr;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
QueueScreen #queue-actions {
|
|
55
|
+
height: 3;
|
|
56
|
+
padding: 1;
|
|
57
|
+
}
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
class RunCancelled(Message):
|
|
61
|
+
def __init__(self, run_id: str):
|
|
62
|
+
self.run_id = run_id
|
|
63
|
+
super().__init__()
|
|
64
|
+
|
|
65
|
+
class RunRetried(Message):
|
|
66
|
+
def __init__(self, run_id: str):
|
|
67
|
+
self.run_id = run_id
|
|
68
|
+
super().__init__()
|
|
69
|
+
|
|
70
|
+
class QueueCleared(Message):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
name: Optional[str] = None,
|
|
76
|
+
id: Optional[str] = None,
|
|
77
|
+
classes: Optional[str] = None,
|
|
78
|
+
):
|
|
79
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
80
|
+
self._selected_run_id: Optional[str] = None
|
|
81
|
+
|
|
82
|
+
def compose(self):
|
|
83
|
+
"""Compose the screen."""
|
|
84
|
+
yield Static("Queue Management", id="queue-header")
|
|
85
|
+
yield DataTable(id="queue-table")
|
|
86
|
+
with Horizontal(id="queue-actions"):
|
|
87
|
+
yield Button("Cancel [C]", id="btn-cancel", variant="error")
|
|
88
|
+
yield Button("Retry [R]", id="btn-retry", variant="warning")
|
|
89
|
+
yield Button("Clear [X]", id="btn-clear", variant="default")
|
|
90
|
+
yield Button("Back [Esc]", id="btn-back", variant="primary")
|
|
91
|
+
yield Footer()
|
|
92
|
+
|
|
93
|
+
def on_mount(self) -> None:
|
|
94
|
+
"""Handle mount."""
|
|
95
|
+
table = self.query_one("#queue-table", DataTable)
|
|
96
|
+
table.add_columns(
|
|
97
|
+
"ID", "Agent", "Input", "State", "Priority",
|
|
98
|
+
"Wait Time", "Duration"
|
|
99
|
+
)
|
|
100
|
+
table.cursor_type = "row"
|
|
101
|
+
|
|
102
|
+
def update_runs(self, runs: List[dict]) -> None:
|
|
103
|
+
"""Update the runs display."""
|
|
104
|
+
table = self.query_one("#queue-table", DataTable)
|
|
105
|
+
table.clear()
|
|
106
|
+
|
|
107
|
+
for run in runs:
|
|
108
|
+
state = run.get("state", "unknown")
|
|
109
|
+
state_style = {
|
|
110
|
+
"queued": "yellow",
|
|
111
|
+
"running": "green",
|
|
112
|
+
"succeeded": "cyan",
|
|
113
|
+
"failed": "red",
|
|
114
|
+
"cancelled": "dim",
|
|
115
|
+
}.get(state, "")
|
|
116
|
+
|
|
117
|
+
state_text = Text(state, style=state_style)
|
|
118
|
+
|
|
119
|
+
input_preview = run.get("input", "")[:40]
|
|
120
|
+
if len(run.get("input", "")) > 40:
|
|
121
|
+
input_preview += "..."
|
|
122
|
+
|
|
123
|
+
table.add_row(
|
|
124
|
+
run.get("run_id", "")[:8],
|
|
125
|
+
run.get("agent_name", ""),
|
|
126
|
+
input_preview,
|
|
127
|
+
state_text,
|
|
128
|
+
run.get("priority", "normal"),
|
|
129
|
+
run.get("wait_time", "-"),
|
|
130
|
+
run.get("duration", "-"),
|
|
131
|
+
key=run.get("run_id"),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
135
|
+
"""Handle row selection."""
|
|
136
|
+
if event.row_key:
|
|
137
|
+
self._selected_run_id = str(event.row_key.value)
|
|
138
|
+
|
|
139
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
140
|
+
"""Handle button press."""
|
|
141
|
+
if event.button.id == "btn-cancel":
|
|
142
|
+
self.action_cancel()
|
|
143
|
+
elif event.button.id == "btn-retry":
|
|
144
|
+
self.action_retry()
|
|
145
|
+
elif event.button.id == "btn-clear":
|
|
146
|
+
self.action_clear()
|
|
147
|
+
elif event.button.id == "btn-back":
|
|
148
|
+
self.action_back()
|
|
149
|
+
|
|
150
|
+
def action_back(self) -> None:
|
|
151
|
+
"""Go back to main screen."""
|
|
152
|
+
self.app.pop_screen()
|
|
153
|
+
|
|
154
|
+
def action_cancel(self) -> None:
|
|
155
|
+
"""Cancel selected run."""
|
|
156
|
+
if self._selected_run_id:
|
|
157
|
+
self.post_message(self.RunCancelled(self._selected_run_id))
|
|
158
|
+
|
|
159
|
+
def action_retry(self) -> None:
|
|
160
|
+
"""Retry selected run."""
|
|
161
|
+
if self._selected_run_id:
|
|
162
|
+
self.post_message(self.RunRetried(self._selected_run_id))
|
|
163
|
+
|
|
164
|
+
def action_clear(self) -> None:
|
|
165
|
+
"""Clear the queue."""
|
|
166
|
+
self.post_message(self.QueueCleared())
|
|
167
|
+
|
|
168
|
+
else:
|
|
169
|
+
class QueueScreen:
|
|
170
|
+
"""Placeholder when Textual is not available."""
|
|
171
|
+
def __init__(self, *args, **kwargs):
|
|
172
|
+
raise ImportError(
|
|
173
|
+
"Textual is required for TUI. Install with: pip install praisonai[tui]"
|
|
174
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session Screen for PraisonAI TUI.
|
|
3
|
+
|
|
4
|
+
Session browser and management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional, List
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from textual.screen import Screen
|
|
11
|
+
from textual.containers import Vertical
|
|
12
|
+
from textual.widgets import Static, Footer, DataTable, Button
|
|
13
|
+
from textual.binding import Binding
|
|
14
|
+
from textual.message import Message
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
TEXTUAL_AVAILABLE = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
TEXTUAL_AVAILABLE = False
|
|
19
|
+
Screen = object
|
|
20
|
+
Message = object
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TEXTUAL_AVAILABLE:
|
|
24
|
+
class SessionScreen(Screen):
|
|
25
|
+
"""
|
|
26
|
+
Session browser screen.
|
|
27
|
+
|
|
28
|
+
Displays recent sessions and allows resuming.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
BINDINGS = [
|
|
32
|
+
Binding("escape", "back", "Back", show=True),
|
|
33
|
+
Binding("enter", "resume", "Resume", show=True),
|
|
34
|
+
Binding("d", "delete", "Delete", show=True),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
DEFAULT_CSS = """
|
|
38
|
+
SessionScreen {
|
|
39
|
+
background: $surface;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
SessionScreen #session-header {
|
|
43
|
+
height: 3;
|
|
44
|
+
background: $primary;
|
|
45
|
+
padding: 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
SessionScreen DataTable {
|
|
49
|
+
height: 1fr;
|
|
50
|
+
}
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
class SessionSelected(Message):
|
|
54
|
+
def __init__(self, session_id: str):
|
|
55
|
+
self.session_id = session_id
|
|
56
|
+
super().__init__()
|
|
57
|
+
|
|
58
|
+
class SessionDeleted(Message):
|
|
59
|
+
def __init__(self, session_id: str):
|
|
60
|
+
self.session_id = session_id
|
|
61
|
+
super().__init__()
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
name: Optional[str] = None,
|
|
66
|
+
id: Optional[str] = None,
|
|
67
|
+
classes: Optional[str] = None,
|
|
68
|
+
):
|
|
69
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
70
|
+
self._selected_session_id: Optional[str] = None
|
|
71
|
+
|
|
72
|
+
def compose(self):
|
|
73
|
+
"""Compose the screen."""
|
|
74
|
+
yield Static("Sessions", id="session-header")
|
|
75
|
+
yield DataTable(id="session-table")
|
|
76
|
+
yield Footer()
|
|
77
|
+
|
|
78
|
+
def on_mount(self) -> None:
|
|
79
|
+
"""Handle mount."""
|
|
80
|
+
table = self.query_one("#session-table", DataTable)
|
|
81
|
+
table.add_columns("ID", "Created", "Updated", "Runs")
|
|
82
|
+
table.cursor_type = "row"
|
|
83
|
+
|
|
84
|
+
def update_sessions(self, sessions: List[dict]) -> None:
|
|
85
|
+
"""Update the sessions display."""
|
|
86
|
+
table = self.query_one("#session-table", DataTable)
|
|
87
|
+
table.clear()
|
|
88
|
+
|
|
89
|
+
for session in sessions:
|
|
90
|
+
table.add_row(
|
|
91
|
+
session.get("session_id", "")[:8],
|
|
92
|
+
session.get("created_at", "-"),
|
|
93
|
+
session.get("updated_at", "-"),
|
|
94
|
+
str(session.get("run_count", 0)),
|
|
95
|
+
key=session.get("session_id"),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
99
|
+
"""Handle row selection."""
|
|
100
|
+
if event.row_key:
|
|
101
|
+
self._selected_session_id = str(event.row_key.value)
|
|
102
|
+
|
|
103
|
+
def action_back(self) -> None:
|
|
104
|
+
"""Go back."""
|
|
105
|
+
self.app.pop_screen()
|
|
106
|
+
|
|
107
|
+
def action_resume(self) -> None:
|
|
108
|
+
"""Resume selected session."""
|
|
109
|
+
if self._selected_session_id:
|
|
110
|
+
self.post_message(self.SessionSelected(self._selected_session_id))
|
|
111
|
+
self.app.pop_screen()
|
|
112
|
+
|
|
113
|
+
def action_delete(self) -> None:
|
|
114
|
+
"""Delete selected session."""
|
|
115
|
+
if self._selected_session_id:
|
|
116
|
+
self.post_message(self.SessionDeleted(self._selected_session_id))
|
|
117
|
+
|
|
118
|
+
else:
|
|
119
|
+
class SessionScreen:
|
|
120
|
+
"""Placeholder when Textual is not available."""
|
|
121
|
+
def __init__(self, *args, **kwargs):
|
|
122
|
+
raise ImportError(
|
|
123
|
+
"Textual is required for TUI. Install with: pip install praisonai[tui]"
|
|
124
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings Screen for PraisonAI TUI.
|
|
3
|
+
|
|
4
|
+
Configuration interface.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from textual.screen import Screen
|
|
11
|
+
from textual.containers import Vertical
|
|
12
|
+
from textual.widgets import Static, Footer, Input, Select, Switch, Button
|
|
13
|
+
from textual.binding import Binding
|
|
14
|
+
from textual.message import Message
|
|
15
|
+
TEXTUAL_AVAILABLE = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
TEXTUAL_AVAILABLE = False
|
|
18
|
+
Screen = object
|
|
19
|
+
Message = object
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TEXTUAL_AVAILABLE:
|
|
23
|
+
class SettingsScreen(Screen):
|
|
24
|
+
"""
|
|
25
|
+
Settings screen.
|
|
26
|
+
|
|
27
|
+
Allows configuration of:
|
|
28
|
+
- Model selection
|
|
29
|
+
- Queue settings
|
|
30
|
+
- Display preferences
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
BINDINGS = [
|
|
34
|
+
Binding("escape", "back", "Back", show=True),
|
|
35
|
+
Binding("s", "save", "Save", show=True),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
DEFAULT_CSS = """
|
|
39
|
+
SettingsScreen {
|
|
40
|
+
background: $surface;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
SettingsScreen #settings-header {
|
|
44
|
+
height: 3;
|
|
45
|
+
background: $primary;
|
|
46
|
+
padding: 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
SettingsScreen .setting-row {
|
|
50
|
+
height: 3;
|
|
51
|
+
padding: 0 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
SettingsScreen .setting-label {
|
|
55
|
+
width: 20;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
SettingsScreen Input {
|
|
59
|
+
width: 40;
|
|
60
|
+
}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
class SettingsSaved(Message):
|
|
64
|
+
def __init__(self, settings: dict):
|
|
65
|
+
self.settings = settings
|
|
66
|
+
super().__init__()
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
current_settings: Optional[dict] = None,
|
|
71
|
+
name: Optional[str] = None,
|
|
72
|
+
id: Optional[str] = None,
|
|
73
|
+
classes: Optional[str] = None,
|
|
74
|
+
):
|
|
75
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
76
|
+
self._settings = current_settings or {}
|
|
77
|
+
|
|
78
|
+
def compose(self):
|
|
79
|
+
"""Compose the screen."""
|
|
80
|
+
yield Static("Settings", id="settings-header")
|
|
81
|
+
|
|
82
|
+
with Vertical(id="settings-form"):
|
|
83
|
+
# Model setting
|
|
84
|
+
yield Static("Model:", classes="setting-label")
|
|
85
|
+
yield Input(
|
|
86
|
+
value=self._settings.get("model", "gpt-4o-mini"),
|
|
87
|
+
id="setting-model",
|
|
88
|
+
placeholder="Model name"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Max concurrent runs
|
|
92
|
+
yield Static("Max Concurrent:", classes="setting-label")
|
|
93
|
+
yield Input(
|
|
94
|
+
value=str(self._settings.get("max_concurrent", 4)),
|
|
95
|
+
id="setting-max-concurrent",
|
|
96
|
+
placeholder="4"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Auto-save interval
|
|
100
|
+
yield Static("Autosave (sec):", classes="setting-label")
|
|
101
|
+
yield Input(
|
|
102
|
+
value=str(self._settings.get("autosave_interval", 30)),
|
|
103
|
+
id="setting-autosave",
|
|
104
|
+
placeholder="30"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Buttons
|
|
108
|
+
yield Button("Save", id="btn-save", variant="primary")
|
|
109
|
+
yield Button("Cancel", id="btn-cancel", variant="default")
|
|
110
|
+
|
|
111
|
+
yield Footer()
|
|
112
|
+
|
|
113
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
114
|
+
"""Handle button press."""
|
|
115
|
+
if event.button.id == "btn-save":
|
|
116
|
+
self.action_save()
|
|
117
|
+
elif event.button.id == "btn-cancel":
|
|
118
|
+
self.action_back()
|
|
119
|
+
|
|
120
|
+
def action_back(self) -> None:
|
|
121
|
+
"""Go back."""
|
|
122
|
+
self.app.pop_screen()
|
|
123
|
+
|
|
124
|
+
def action_save(self) -> None:
|
|
125
|
+
"""Save settings."""
|
|
126
|
+
try:
|
|
127
|
+
model_input = self.query_one("#setting-model", Input)
|
|
128
|
+
max_concurrent_input = self.query_one("#setting-max-concurrent", Input)
|
|
129
|
+
autosave_input = self.query_one("#setting-autosave", Input)
|
|
130
|
+
|
|
131
|
+
settings = {
|
|
132
|
+
"model": model_input.value,
|
|
133
|
+
"max_concurrent": int(max_concurrent_input.value or "4"),
|
|
134
|
+
"autosave_interval": int(autosave_input.value or "30"),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
self.post_message(self.SettingsSaved(settings))
|
|
138
|
+
self.app.pop_screen()
|
|
139
|
+
except ValueError:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
else:
|
|
143
|
+
class SettingsScreen:
|
|
144
|
+
"""Placeholder when Textual is not available."""
|
|
145
|
+
def __init__(self, *args, **kwargs):
|
|
146
|
+
raise ImportError(
|
|
147
|
+
"Textual is required for TUI. Install with: pip install praisonai[tui]"
|
|
148
|
+
)
|