praisonai-code 0.0.1__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_code/__init__.py +17 -0
- praisonai_code/cli/__init__.py +12 -0
- praisonai_code/cli/_forward_shim.py +10 -0
- praisonai_code/cli/_paths.py +88 -0
- praisonai_code/cli/_warnings.py +58 -0
- praisonai_code/cli/app.py +757 -0
- praisonai_code/cli/approval_backend.py +272 -0
- praisonai_code/cli/branding.py +94 -0
- praisonai_code/cli/commands/__init__.py +114 -0
- praisonai_code/cli/commands/acp.py +80 -0
- praisonai_code/cli/commands/agent.py +116 -0
- praisonai_code/cli/commands/agents.py +80 -0
- praisonai_code/cli/commands/app.py +139 -0
- praisonai_code/cli/commands/attach.py +95 -0
- praisonai_code/cli/commands/audit.py +102 -0
- praisonai_code/cli/commands/auth.py +508 -0
- praisonai_code/cli/commands/batch.py +848 -0
- praisonai_code/cli/commands/benchmark.py +286 -0
- praisonai_code/cli/commands/browser.py +299 -0
- praisonai_code/cli/commands/call.py +45 -0
- praisonai_code/cli/commands/chat.py +332 -0
- praisonai_code/cli/commands/checkpoint.py +170 -0
- praisonai_code/cli/commands/code.py +276 -0
- praisonai_code/cli/commands/command.py +114 -0
- praisonai_code/cli/commands/commit.py +47 -0
- praisonai_code/cli/commands/completion.py +333 -0
- praisonai_code/cli/commands/config.py +681 -0
- praisonai_code/cli/commands/context.py +414 -0
- praisonai_code/cli/commands/daemon.py +203 -0
- praisonai_code/cli/commands/debug.py +142 -0
- praisonai_code/cli/commands/deploy.py +71 -0
- praisonai_code/cli/commands/diag.py +55 -0
- praisonai_code/cli/commands/docs.py +1575 -0
- praisonai_code/cli/commands/doctor.py +332 -0
- praisonai_code/cli/commands/endpoints.py +51 -0
- praisonai_code/cli/commands/environment.py +179 -0
- praisonai_code/cli/commands/eval.py +131 -0
- praisonai_code/cli/commands/examples.py +953 -0
- praisonai_code/cli/commands/flow.py +436 -0
- praisonai_code/cli/commands/github.py +752 -0
- praisonai_code/cli/commands/hooks.py +74 -0
- praisonai_code/cli/commands/init.py +174 -0
- praisonai_code/cli/commands/knowledge.py +440 -0
- praisonai_code/cli/commands/langextract.py +120 -0
- praisonai_code/cli/commands/langfuse.py +984 -0
- praisonai_code/cli/commands/loop.py +211 -0
- praisonai_code/cli/commands/lsp.py +112 -0
- praisonai_code/cli/commands/managed.py +659 -0
- praisonai_code/cli/commands/mcp.py +763 -0
- praisonai_code/cli/commands/memory.py +298 -0
- praisonai_code/cli/commands/models.py +264 -0
- praisonai_code/cli/commands/n8n.py +326 -0
- praisonai_code/cli/commands/obs.py +19 -0
- praisonai_code/cli/commands/package.py +76 -0
- praisonai_code/cli/commands/paths.py +106 -0
- praisonai_code/cli/commands/permissions.py +272 -0
- praisonai_code/cli/commands/plugins.py +609 -0
- praisonai_code/cli/commands/port.py +530 -0
- praisonai_code/cli/commands/profile.py +466 -0
- praisonai_code/cli/commands/publish.py +193 -0
- praisonai_code/cli/commands/rag.py +913 -0
- praisonai_code/cli/commands/realtime.py +52 -0
- praisonai_code/cli/commands/recipe.py +684 -0
- praisonai_code/cli/commands/registry.py +59 -0
- praisonai_code/cli/commands/replay.py +830 -0
- praisonai_code/cli/commands/research.py +49 -0
- praisonai_code/cli/commands/retrieval.py +377 -0
- praisonai_code/cli/commands/rules.py +71 -0
- praisonai_code/cli/commands/run.py +1573 -0
- praisonai_code/cli/commands/sandbox.py +371 -0
- praisonai_code/cli/commands/schedule.py +529 -0
- praisonai_code/cli/commands/serve.py +690 -0
- praisonai_code/cli/commands/session.py +450 -0
- praisonai_code/cli/commands/setup.py +174 -0
- praisonai_code/cli/commands/skills.py +545 -0
- praisonai_code/cli/commands/standardise.py +711 -0
- praisonai_code/cli/commands/templates.py +54 -0
- praisonai_code/cli/commands/test.py +558 -0
- praisonai_code/cli/commands/todo.py +74 -0
- praisonai_code/cli/commands/tools.py +205 -0
- praisonai_code/cli/commands/traces.py +145 -0
- praisonai_code/cli/commands/tracker.py +852 -0
- praisonai_code/cli/commands/train.py +613 -0
- praisonai_code/cli/commands/ui.py +172 -0
- praisonai_code/cli/commands/up.py +354 -0
- praisonai_code/cli/commands/validate.py +291 -0
- praisonai_code/cli/commands/version.py +101 -0
- praisonai_code/cli/commands/workflow.py +97 -0
- praisonai_code/cli/config_loader.py +437 -0
- praisonai_code/cli/configuration/__init__.py +27 -0
- praisonai_code/cli/configuration/config.schema.json +57 -0
- praisonai_code/cli/configuration/credentials.py +446 -0
- praisonai_code/cli/configuration/loader.py +364 -0
- praisonai_code/cli/configuration/model_resolver.py +161 -0
- praisonai_code/cli/configuration/oauth.py +389 -0
- praisonai_code/cli/configuration/paths.py +224 -0
- praisonai_code/cli/configuration/resolver.py +687 -0
- praisonai_code/cli/configuration/schema.py +317 -0
- praisonai_code/cli/execution/__init__.py +99 -0
- praisonai_code/cli/execution/core.py +208 -0
- praisonai_code/cli/execution/profiler.py +898 -0
- praisonai_code/cli/execution/request.py +85 -0
- praisonai_code/cli/execution/result.py +74 -0
- praisonai_code/cli/fallback_schema.py +416 -0
- praisonai_code/cli/features/__init__.py +278 -0
- praisonai_code/cli/features/_endpoint_registry.py +64 -0
- praisonai_code/cli/features/_search_registry.py +43 -0
- praisonai_code/cli/features/acp.py +236 -0
- praisonai_code/cli/features/action_orchestrator.py +576 -0
- praisonai_code/cli/features/agent_scheduler.py +773 -0
- praisonai_code/cli/features/agent_tools.py +603 -0
- praisonai_code/cli/features/agents.py +397 -0
- praisonai_code/cli/features/at_mentions.py +471 -0
- praisonai_code/cli/features/audit_cli.py +270 -0
- praisonai_code/cli/features/auto_memory.py +182 -0
- praisonai_code/cli/features/auto_mode.py +552 -0
- praisonai_code/cli/features/autonomy_mode.py +546 -0
- praisonai_code/cli/features/background.py +356 -0
- praisonai_code/cli/features/base.py +168 -0
- praisonai_code/cli/features/benchmark.py +1462 -0
- praisonai_code/cli/features/capabilities.py +1326 -0
- praisonai_code/cli/features/checkpoints.py +345 -0
- praisonai_code/cli/features/cli_profiler.py +335 -0
- praisonai_code/cli/features/code_intelligence.py +666 -0
- praisonai_code/cli/features/compaction.py +294 -0
- praisonai_code/cli/features/compare.py +534 -0
- praisonai_code/cli/features/config_hierarchy.py +366 -0
- praisonai_code/cli/features/context_manager.py +597 -0
- praisonai_code/cli/features/cost_tracker.py +514 -0
- praisonai_code/cli/features/csv_test_runner.py +736 -0
- praisonai_code/cli/features/custom_definitions.py +790 -0
- praisonai_code/cli/features/debug.py +810 -0
- praisonai_code/cli/features/deploy.py +605 -0
- praisonai_code/cli/features/diag.py +289 -0
- praisonai_code/cli/features/display_jsonl.py +173 -0
- praisonai_code/cli/features/doctor/__init__.py +63 -0
- praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
- praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
- praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
- praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
- praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
- praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
- praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
- praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
- praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
- praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
- praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai_code/cli/features/doctor/engine.py +266 -0
- praisonai_code/cli/features/doctor/formatters.py +377 -0
- praisonai_code/cli/features/doctor/handler.py +564 -0
- praisonai_code/cli/features/doctor/models.py +276 -0
- praisonai_code/cli/features/doctor/registry.py +239 -0
- praisonai_code/cli/features/endpoints.py +1016 -0
- praisonai_code/cli/features/eval.py +559 -0
- praisonai_code/cli/features/examples.py +707 -0
- praisonai_code/cli/features/external_agents.py +231 -0
- praisonai_code/cli/features/fast_context.py +410 -0
- praisonai_code/cli/features/file_history.py +320 -0
- praisonai_code/cli/features/flow_display.py +566 -0
- praisonai_code/cli/features/git_attribution.py +159 -0
- praisonai_code/cli/features/git_integration.py +651 -0
- praisonai_code/cli/features/guardrail.py +171 -0
- praisonai_code/cli/features/handoff.py +252 -0
- praisonai_code/cli/features/hooks.py +583 -0
- praisonai_code/cli/features/hybrid_workflow.py +391 -0
- praisonai_code/cli/features/image.py +384 -0
- praisonai_code/cli/features/interactive_core_headless.py +450 -0
- praisonai_code/cli/features/interactive_runtime.py +600 -0
- praisonai_code/cli/features/interactive_test_harness.py +537 -0
- praisonai_code/cli/features/interactive_tools.py +428 -0
- praisonai_code/cli/features/interactive_tui.py +603 -0
- praisonai_code/cli/features/job_workflow.py +906 -0
- praisonai_code/cli/features/jobs.py +632 -0
- praisonai_code/cli/features/knowledge.py +531 -0
- praisonai_code/cli/features/knowledge_cli.py +438 -0
- praisonai_code/cli/features/lite.py +244 -0
- praisonai_code/cli/features/logs.py +200 -0
- praisonai_code/cli/features/lsp_cli.py +225 -0
- praisonai_code/cli/features/lsp_diagnostics.py +185 -0
- praisonai_code/cli/features/mcp.py +344 -0
- praisonai_code/cli/features/message_queue.py +587 -0
- praisonai_code/cli/features/metrics.py +210 -0
- praisonai_code/cli/features/migrate.py +1329 -0
- praisonai_code/cli/features/migration_flow.py +463 -0
- praisonai_code/cli/features/migration_spec.py +276 -0
- praisonai_code/cli/features/n8n.py +703 -0
- praisonai_code/cli/features/observability.py +293 -0
- praisonai_code/cli/features/ollama.py +361 -0
- praisonai_code/cli/features/output_modes.py +155 -0
- praisonai_code/cli/features/output_style.py +273 -0
- praisonai_code/cli/features/package.py +631 -0
- praisonai_code/cli/features/performance.py +308 -0
- praisonai_code/cli/features/persistence.py +636 -0
- praisonai_code/cli/features/profiler/__init__.py +81 -0
- praisonai_code/cli/features/profiler/core.py +558 -0
- praisonai_code/cli/features/profiler/optimizations.py +652 -0
- praisonai_code/cli/features/profiler/suite.py +386 -0
- praisonai_code/cli/features/queue/__init__.py +73 -0
- praisonai_code/cli/features/queue/manager.py +435 -0
- praisonai_code/cli/features/queue/models.py +289 -0
- praisonai_code/cli/features/queue/persistence.py +564 -0
- praisonai_code/cli/features/queue/scheduler.py +529 -0
- praisonai_code/cli/features/queue/worker.py +400 -0
- praisonai_code/cli/features/recipe.py +2187 -0
- praisonai_code/cli/features/recipe_creator.py +996 -0
- praisonai_code/cli/features/recipe_optimizer.py +1364 -0
- praisonai_code/cli/features/recipe_prompts.py +226 -0
- praisonai_code/cli/features/registry.py +229 -0
- praisonai_code/cli/features/repo_map.py +860 -0
- praisonai_code/cli/features/router.py +466 -0
- praisonai_code/cli/features/safe_shell.py +427 -0
- praisonai_code/cli/features/sandbox_cli.py +283 -0
- praisonai_code/cli/features/sandbox_executor.py +536 -0
- praisonai_code/cli/features/sdk_knowledge.py +500 -0
- praisonai_code/cli/features/session.py +222 -0
- praisonai_code/cli/features/session_checkpoints.py +208 -0
- praisonai_code/cli/features/setup/__init__.py +9 -0
- praisonai_code/cli/features/setup/handler.py +355 -0
- praisonai_code/cli/features/setup/templates.py +62 -0
- praisonai_code/cli/features/skills.py +940 -0
- praisonai_code/cli/features/slash_commands.py +692 -0
- praisonai_code/cli/features/telemetry.py +179 -0
- praisonai_code/cli/features/templates.py +1390 -0
- praisonai_code/cli/features/thinking.py +343 -0
- praisonai_code/cli/features/todo.py +334 -0
- praisonai_code/cli/features/tools.py +680 -0
- praisonai_code/cli/features/tui/__init__.py +83 -0
- praisonai_code/cli/features/tui/app.py +871 -0
- praisonai_code/cli/features/tui/cli.py +580 -0
- praisonai_code/cli/features/tui/config.py +150 -0
- praisonai_code/cli/features/tui/debug.py +526 -0
- praisonai_code/cli/features/tui/events.py +99 -0
- praisonai_code/cli/features/tui/mock_provider.py +328 -0
- praisonai_code/cli/features/tui/orchestrator.py +652 -0
- praisonai_code/cli/features/tui/screens/__init__.py +50 -0
- praisonai_code/cli/features/tui/screens/help.py +157 -0
- praisonai_code/cli/features/tui/screens/main.py +568 -0
- praisonai_code/cli/features/tui/screens/queue.py +174 -0
- praisonai_code/cli/features/tui/screens/session.py +124 -0
- praisonai_code/cli/features/tui/screens/settings.py +148 -0
- praisonai_code/cli/features/tui/session_store.py +198 -0
- praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
- praisonai_code/cli/features/tui/widgets/chat.py +263 -0
- praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
- praisonai_code/cli/features/tui/widgets/composer.py +292 -0
- praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
- praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
- praisonai_code/cli/features/tui/widgets/status.py +181 -0
- praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
- praisonai_code/cli/features/wizard.py +289 -0
- praisonai_code/cli/features/workflow.py +802 -0
- praisonai_code/cli/features/yaml_utils.py +321 -0
- praisonai_code/cli/interactive/__init__.py +48 -0
- praisonai_code/cli/interactive/async_tui.py +1218 -0
- praisonai_code/cli/interactive/config.py +139 -0
- praisonai_code/cli/interactive/core.py +618 -0
- praisonai_code/cli/interactive/events.py +131 -0
- praisonai_code/cli/interactive/frontends/__init__.py +31 -0
- praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
- praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
- praisonai_code/cli/interactive/praison_io.py +502 -0
- praisonai_code/cli/interactive/repl.py +297 -0
- praisonai_code/cli/interactive/split_tui.py +456 -0
- praisonai_code/cli/interactive/tui_app.py +457 -0
- praisonai_code/cli/langfuse_client.py +360 -0
- praisonai_code/cli/main.py +7421 -0
- praisonai_code/cli/output/__init__.py +25 -0
- praisonai_code/cli/output/console.py +456 -0
- praisonai_code/cli/output/event_bridge.py +191 -0
- praisonai_code/cli/schedule_cli.py +54 -0
- praisonai_code/cli/schema_provider.py +23 -0
- praisonai_code/cli/session/__init__.py +16 -0
- praisonai_code/cli/session/resume.py +148 -0
- praisonai_code/cli/session/unified.py +548 -0
- praisonai_code/cli/state/__init__.py +31 -0
- praisonai_code/cli/state/identifiers.py +161 -0
- praisonai_code/cli/state/project_sessions.py +383 -0
- praisonai_code/cli/state/sessions.py +390 -0
- praisonai_code/cli/ui/__init__.py +160 -0
- praisonai_code/cli/ui/config.py +46 -0
- praisonai_code/cli/ui/events.py +61 -0
- praisonai_code/cli/ui/mg_backend.py +342 -0
- praisonai_code/cli/ui/plain.py +133 -0
- praisonai_code/cli/ui/rich_backend.py +162 -0
- praisonai_code/cli/unified_schema.py +655 -0
- praisonai_code/cli/utils/env_utils.py +126 -0
- praisonai_code/cli/utils/project.py +131 -0
- praisonai_code/cli_backends/__init__.py +73 -0
- praisonai_code/cli_backends/claude.py +373 -0
- praisonai_code/cli_backends/registry.py +113 -0
- praisonai_code/runtime/__init__.py +36 -0
- praisonai_code/runtime/__main__.py +81 -0
- praisonai_code/runtime/client.py +131 -0
- praisonai_code/runtime/descriptor.py +209 -0
- praisonai_code/runtime/server.py +356 -0
- praisonai_code-0.0.1.dist-info/METADATA +80 -0
- praisonai_code-0.0.1.dist-info/RECORD +309 -0
- praisonai_code-0.0.1.dist-info/WHEEL +5 -0
- praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PraisonAI Split-Pane TUI Application.
|
|
3
|
+
|
|
4
|
+
Full-screen terminal UI with:
|
|
5
|
+
- Fixed bottom input pane (always visible)
|
|
6
|
+
- Scrollable top output pane
|
|
7
|
+
- Direct response (no streaming - faster)
|
|
8
|
+
- Activity indicators showing agent status
|
|
9
|
+
|
|
10
|
+
Uses prompt_toolkit Application for true split-pane layout.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import shutil
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List, Optional
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
|
|
21
|
+
# ============================================================================
|
|
22
|
+
# Branding - Import from unified source
|
|
23
|
+
# ============================================================================
|
|
24
|
+
|
|
25
|
+
from praisonai.cli.branding import get_logo, get_version
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ============================================================================
|
|
29
|
+
# Configuration
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class SplitTUIConfig:
|
|
34
|
+
"""Configuration for the Split TUI application."""
|
|
35
|
+
model: str = "gpt-4o-mini"
|
|
36
|
+
show_logo: bool = True
|
|
37
|
+
show_status_bar: bool = True
|
|
38
|
+
session_id: Optional[str] = None
|
|
39
|
+
workspace: Optional[str] = None
|
|
40
|
+
history_file: Optional[str] = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ============================================================================
|
|
44
|
+
# Message Types
|
|
45
|
+
# ============================================================================
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ChatMessage:
|
|
49
|
+
"""A chat message."""
|
|
50
|
+
role: str # "user", "assistant", "system", "status"
|
|
51
|
+
content: str
|
|
52
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ============================================================================
|
|
56
|
+
# Split TUI Application
|
|
57
|
+
# ============================================================================
|
|
58
|
+
|
|
59
|
+
class SplitTUI:
|
|
60
|
+
"""
|
|
61
|
+
Split-pane TUI with fixed bottom input and scrollable top output.
|
|
62
|
+
|
|
63
|
+
Key features:
|
|
64
|
+
- Input box ALWAYS visible at bottom
|
|
65
|
+
- Output scrolls above independently
|
|
66
|
+
- Direct response (no streaming) for speed
|
|
67
|
+
- Activity indicators during processing
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, config: Optional[SplitTUIConfig] = None):
|
|
71
|
+
self.config = config or SplitTUIConfig()
|
|
72
|
+
self.messages: List[ChatMessage] = []
|
|
73
|
+
self._running = False
|
|
74
|
+
self._agent = None
|
|
75
|
+
self._processing = False
|
|
76
|
+
self._status_text = ""
|
|
77
|
+
|
|
78
|
+
# Terminal size
|
|
79
|
+
self.term_width, self.term_height = shutil.get_terminal_size((80, 24))
|
|
80
|
+
|
|
81
|
+
# Session ID
|
|
82
|
+
import uuid
|
|
83
|
+
self.session_id = self.config.session_id or str(uuid.uuid4())[:8]
|
|
84
|
+
|
|
85
|
+
def _get_agent(self):
|
|
86
|
+
"""Lazy-load the agent with verbose=False for no streaming."""
|
|
87
|
+
if self._agent is None:
|
|
88
|
+
try:
|
|
89
|
+
from praisonaiagents import Agent
|
|
90
|
+
|
|
91
|
+
self._agent = Agent(
|
|
92
|
+
name="Praison",
|
|
93
|
+
role="AI Assistant",
|
|
94
|
+
goal="Help the user with their requests",
|
|
95
|
+
instructions="You are Praison AI, a helpful assistant. Be concise and helpful.",
|
|
96
|
+
llm=self.config.model,
|
|
97
|
+
)
|
|
98
|
+
except ImportError as e:
|
|
99
|
+
raise RuntimeError(f"Failed to import praisonaiagents: {e}")
|
|
100
|
+
|
|
101
|
+
return self._agent
|
|
102
|
+
|
|
103
|
+
def _format_output(self) -> str:
|
|
104
|
+
"""Format all messages for the output pane."""
|
|
105
|
+
lines = []
|
|
106
|
+
|
|
107
|
+
# Logo
|
|
108
|
+
if self.config.show_logo and not self.messages:
|
|
109
|
+
logo = get_logo(self.term_width)
|
|
110
|
+
lines.append(logo.strip())
|
|
111
|
+
lines.append("")
|
|
112
|
+
lines.append(f" v{get_version()} · Model: {self.config.model}")
|
|
113
|
+
lines.append("")
|
|
114
|
+
lines.append(" Type your message and press Enter. Use /help for commands.")
|
|
115
|
+
lines.append("")
|
|
116
|
+
|
|
117
|
+
# Messages
|
|
118
|
+
for msg in self.messages:
|
|
119
|
+
if msg.role == "user":
|
|
120
|
+
lines.append(f"› {msg.content}")
|
|
121
|
+
lines.append("")
|
|
122
|
+
elif msg.role == "assistant":
|
|
123
|
+
lines.append(f"● {msg.content}")
|
|
124
|
+
lines.append("")
|
|
125
|
+
elif msg.role == "system":
|
|
126
|
+
lines.append(f" {msg.content}")
|
|
127
|
+
lines.append("")
|
|
128
|
+
elif msg.role == "status":
|
|
129
|
+
lines.append(f" ⏳ {msg.content}")
|
|
130
|
+
|
|
131
|
+
# Processing indicator
|
|
132
|
+
if self._processing:
|
|
133
|
+
lines.append(f" ⏳ {self._status_text}")
|
|
134
|
+
|
|
135
|
+
return "\n".join(lines)
|
|
136
|
+
|
|
137
|
+
def _handle_command(self, command: str) -> bool:
|
|
138
|
+
"""Handle slash commands. Returns True if handled."""
|
|
139
|
+
parts = command.split(maxsplit=1)
|
|
140
|
+
cmd = parts[0].lower().lstrip("/")
|
|
141
|
+
args = parts[1] if len(parts) > 1 else ""
|
|
142
|
+
|
|
143
|
+
if cmd in ("exit", "quit", "q"):
|
|
144
|
+
self._running = False
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
elif cmd == "help":
|
|
148
|
+
help_text = """Available Commands:
|
|
149
|
+
/help Show this help
|
|
150
|
+
/exit, /quit Exit Praison AI
|
|
151
|
+
/clear Clear conversation
|
|
152
|
+
/model [name] Show or change model
|
|
153
|
+
/new Start new conversation"""
|
|
154
|
+
self.messages.append(ChatMessage(role="system", content=help_text))
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
elif cmd == "clear":
|
|
158
|
+
self.messages.clear()
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
elif cmd == "new":
|
|
162
|
+
self.messages.clear()
|
|
163
|
+
import uuid
|
|
164
|
+
self.session_id = str(uuid.uuid4())[:8]
|
|
165
|
+
self.messages.append(ChatMessage(role="system", content=f"New session: {self.session_id}"))
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
elif cmd == "model":
|
|
169
|
+
if args:
|
|
170
|
+
self.config.model = args
|
|
171
|
+
self._agent = None
|
|
172
|
+
self.messages.append(ChatMessage(role="system", content=f"Model changed to: {args}"))
|
|
173
|
+
else:
|
|
174
|
+
self.messages.append(ChatMessage(role="system", content=f"Current model: {self.config.model}"))
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
self.messages.append(ChatMessage(role="system", content=f"Unknown command: /{cmd}. Use /help for available commands."))
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
def _execute_prompt(self, prompt: str) -> Optional[str]:
|
|
182
|
+
"""Execute a prompt and return the response (direct, no streaming)."""
|
|
183
|
+
import sys
|
|
184
|
+
import io
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
agent = self._get_agent()
|
|
188
|
+
|
|
189
|
+
# Suppress agent's Rich output by redirecting stdout/stderr
|
|
190
|
+
old_stdout = sys.stdout
|
|
191
|
+
old_stderr = sys.stderr
|
|
192
|
+
sys.stdout = io.StringIO()
|
|
193
|
+
sys.stderr = io.StringIO()
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
response = agent.start(prompt)
|
|
197
|
+
finally:
|
|
198
|
+
# Restore stdout/stderr
|
|
199
|
+
sys.stdout = old_stdout
|
|
200
|
+
sys.stderr = old_stderr
|
|
201
|
+
|
|
202
|
+
return str(response) if response else None
|
|
203
|
+
except Exception as e:
|
|
204
|
+
return f"Error: {e}"
|
|
205
|
+
|
|
206
|
+
def _run_with_status(self, prompt: str) -> Optional[str]:
|
|
207
|
+
"""Run prompt with status updates."""
|
|
208
|
+
self._processing = True
|
|
209
|
+
self._status_text = "Praison AI is thinking..."
|
|
210
|
+
|
|
211
|
+
# Execute in background
|
|
212
|
+
result = [None]
|
|
213
|
+
error = [None]
|
|
214
|
+
|
|
215
|
+
def execute():
|
|
216
|
+
try:
|
|
217
|
+
result[0] = self._execute_prompt(prompt)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
error[0] = str(e)
|
|
220
|
+
|
|
221
|
+
thread = threading.Thread(target=execute)
|
|
222
|
+
thread.start()
|
|
223
|
+
|
|
224
|
+
# Update status while waiting
|
|
225
|
+
start_time = time.time()
|
|
226
|
+
spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
227
|
+
idx = 0
|
|
228
|
+
|
|
229
|
+
while thread.is_alive():
|
|
230
|
+
elapsed = time.time() - start_time
|
|
231
|
+
self._status_text = f"{spinner[idx]} Praison AI is thinking... ({elapsed:.1f}s)"
|
|
232
|
+
idx = (idx + 1) % len(spinner)
|
|
233
|
+
time.sleep(0.1)
|
|
234
|
+
|
|
235
|
+
thread.join()
|
|
236
|
+
self._processing = False
|
|
237
|
+
self._status_text = ""
|
|
238
|
+
|
|
239
|
+
if error[0]:
|
|
240
|
+
return f"Error: {error[0]}"
|
|
241
|
+
return result[0]
|
|
242
|
+
|
|
243
|
+
def run(self) -> None:
|
|
244
|
+
"""Run the split-pane TUI."""
|
|
245
|
+
self._running = True
|
|
246
|
+
|
|
247
|
+
# Try to use prompt_toolkit for better experience
|
|
248
|
+
try:
|
|
249
|
+
self._run_with_prompt_toolkit()
|
|
250
|
+
except ImportError:
|
|
251
|
+
# Fallback to simple mode
|
|
252
|
+
self._run_simple()
|
|
253
|
+
|
|
254
|
+
def _run_simple(self) -> None:
|
|
255
|
+
"""Simple fallback mode without prompt_toolkit."""
|
|
256
|
+
# Print welcome
|
|
257
|
+
print(self._format_output())
|
|
258
|
+
|
|
259
|
+
while self._running:
|
|
260
|
+
try:
|
|
261
|
+
# Status bar
|
|
262
|
+
if self.config.show_status_bar:
|
|
263
|
+
status = f" ? for help | {self.config.model} | Session: {self.session_id} "
|
|
264
|
+
print(f"\033[90m{status}\033[0m")
|
|
265
|
+
|
|
266
|
+
# Get input
|
|
267
|
+
user_input = input("› ").strip()
|
|
268
|
+
|
|
269
|
+
if not user_input:
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
# Handle commands
|
|
273
|
+
if user_input.startswith("/"):
|
|
274
|
+
self._handle_command(user_input)
|
|
275
|
+
# Print system messages
|
|
276
|
+
for msg in self.messages:
|
|
277
|
+
if msg.role == "system":
|
|
278
|
+
print(f" {msg.content}")
|
|
279
|
+
self.messages = [m for m in self.messages if m.role != "system"]
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
# Add user message
|
|
283
|
+
self.messages.append(ChatMessage(role="user", content=user_input))
|
|
284
|
+
|
|
285
|
+
# Execute with status
|
|
286
|
+
print(" ⏳ Praison AI is thinking...")
|
|
287
|
+
response = self._execute_prompt(user_input)
|
|
288
|
+
|
|
289
|
+
if response:
|
|
290
|
+
self.messages.append(ChatMessage(role="assistant", content=response))
|
|
291
|
+
print(f"\n● {response}\n")
|
|
292
|
+
|
|
293
|
+
except KeyboardInterrupt:
|
|
294
|
+
print("\n Use /exit to quit")
|
|
295
|
+
except EOFError:
|
|
296
|
+
self._running = False
|
|
297
|
+
|
|
298
|
+
print("\n Goodbye from Praison AI!")
|
|
299
|
+
|
|
300
|
+
def _run_with_prompt_toolkit(self) -> None:
|
|
301
|
+
"""Run with prompt_toolkit for split-pane layout."""
|
|
302
|
+
from prompt_toolkit import Application
|
|
303
|
+
from prompt_toolkit.buffer import Buffer
|
|
304
|
+
from prompt_toolkit.layout.containers import HSplit, VSplit, Window
|
|
305
|
+
from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
|
|
306
|
+
from prompt_toolkit.layout.layout import Layout
|
|
307
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
308
|
+
from prompt_toolkit.styles import Style
|
|
309
|
+
from prompt_toolkit.history import FileHistory
|
|
310
|
+
|
|
311
|
+
# Setup history
|
|
312
|
+
history_file = self.config.history_file or str(Path.home() / ".praison" / "history")
|
|
313
|
+
Path(history_file).parent.mkdir(parents=True, exist_ok=True)
|
|
314
|
+
|
|
315
|
+
# Output buffer (read-only, for displaying conversation)
|
|
316
|
+
output_text = [self._format_output()]
|
|
317
|
+
|
|
318
|
+
def get_output_text():
|
|
319
|
+
return output_text[0]
|
|
320
|
+
|
|
321
|
+
# Input buffer
|
|
322
|
+
input_buffer = Buffer(
|
|
323
|
+
multiline=False,
|
|
324
|
+
history=FileHistory(history_file),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Key bindings
|
|
328
|
+
kb = KeyBindings()
|
|
329
|
+
|
|
330
|
+
@kb.add("enter")
|
|
331
|
+
def handle_enter(event):
|
|
332
|
+
user_input = input_buffer.text.strip()
|
|
333
|
+
if not user_input:
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
input_buffer.reset()
|
|
337
|
+
|
|
338
|
+
# Handle commands
|
|
339
|
+
if user_input.startswith("/"):
|
|
340
|
+
self._handle_command(user_input)
|
|
341
|
+
output_text[0] = self._format_output()
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
# Add user message
|
|
345
|
+
self.messages.append(ChatMessage(role="user", content=user_input))
|
|
346
|
+
|
|
347
|
+
# Show processing status
|
|
348
|
+
self.messages.append(ChatMessage(role="status", content="Praison AI is thinking..."))
|
|
349
|
+
output_text[0] = self._format_output()
|
|
350
|
+
|
|
351
|
+
# Execute (this blocks but that's OK for direct response)
|
|
352
|
+
response = self._execute_prompt(user_input)
|
|
353
|
+
|
|
354
|
+
# Remove status message
|
|
355
|
+
self.messages = [m for m in self.messages if m.role != "status"]
|
|
356
|
+
|
|
357
|
+
if response:
|
|
358
|
+
self.messages.append(ChatMessage(role="assistant", content=response))
|
|
359
|
+
|
|
360
|
+
output_text[0] = self._format_output()
|
|
361
|
+
|
|
362
|
+
@kb.add("c-c")
|
|
363
|
+
def handle_ctrl_c(event):
|
|
364
|
+
input_buffer.reset()
|
|
365
|
+
|
|
366
|
+
@kb.add("c-d")
|
|
367
|
+
def handle_ctrl_d(event):
|
|
368
|
+
if not input_buffer.text:
|
|
369
|
+
self._running = False
|
|
370
|
+
event.app.exit()
|
|
371
|
+
|
|
372
|
+
@kb.add("c-q")
|
|
373
|
+
def handle_ctrl_q(event):
|
|
374
|
+
self._running = False
|
|
375
|
+
event.app.exit()
|
|
376
|
+
|
|
377
|
+
# Style
|
|
378
|
+
style = Style.from_dict({
|
|
379
|
+
"output-area": "bg:#0d0d0d",
|
|
380
|
+
"input-area": "bg:#1a1a1a",
|
|
381
|
+
"status-bar": "bg:#333333 #888888",
|
|
382
|
+
"prompt": "#00aaff bold",
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
# Status bar text
|
|
386
|
+
def get_status_bar():
|
|
387
|
+
left = "? for help"
|
|
388
|
+
center = self.config.model
|
|
389
|
+
right = f"Session: {self.session_id}"
|
|
390
|
+
return f" {left} | {center} | {right} "
|
|
391
|
+
|
|
392
|
+
# Layout: Output (top, scrollable) + Status bar + Input (bottom, fixed)
|
|
393
|
+
layout = Layout(
|
|
394
|
+
HSplit([
|
|
395
|
+
# Output area (takes remaining space)
|
|
396
|
+
Window(
|
|
397
|
+
content=FormattedTextControl(get_output_text),
|
|
398
|
+
wrap_lines=True,
|
|
399
|
+
style="class:output-area",
|
|
400
|
+
),
|
|
401
|
+
# Status bar (1 line)
|
|
402
|
+
Window(
|
|
403
|
+
content=FormattedTextControl(get_status_bar),
|
|
404
|
+
height=1,
|
|
405
|
+
style="class:status-bar",
|
|
406
|
+
),
|
|
407
|
+
# Input area (fixed at bottom, 3 lines for visibility)
|
|
408
|
+
HSplit([
|
|
409
|
+
Window(height=1), # Spacer
|
|
410
|
+
VSplit([
|
|
411
|
+
Window(
|
|
412
|
+
content=FormattedTextControl([("class:prompt", "› ")]),
|
|
413
|
+
width=2,
|
|
414
|
+
),
|
|
415
|
+
Window(
|
|
416
|
+
content=BufferControl(buffer=input_buffer),
|
|
417
|
+
wrap_lines=True,
|
|
418
|
+
),
|
|
419
|
+
]),
|
|
420
|
+
Window(height=1), # Spacer
|
|
421
|
+
], height=3, style="class:input-area"),
|
|
422
|
+
])
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# Create application
|
|
426
|
+
app = Application(
|
|
427
|
+
layout=layout,
|
|
428
|
+
key_bindings=kb,
|
|
429
|
+
style=style,
|
|
430
|
+
full_screen=True,
|
|
431
|
+
mouse_support=True,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# Run
|
|
435
|
+
app.run()
|
|
436
|
+
|
|
437
|
+
print("\n Goodbye from Praison AI!")
|
|
438
|
+
|
|
439
|
+
def run_single(self, prompt: str) -> Optional[str]:
|
|
440
|
+
"""Run a single prompt (non-interactive)."""
|
|
441
|
+
return self._execute_prompt(prompt)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def start_split_tui(
|
|
445
|
+
model: str = "gpt-4o-mini",
|
|
446
|
+
show_logo: bool = True,
|
|
447
|
+
**kwargs
|
|
448
|
+
) -> None:
|
|
449
|
+
"""Start the split-pane TUI application."""
|
|
450
|
+
config = SplitTUIConfig(
|
|
451
|
+
model=model,
|
|
452
|
+
show_logo=show_logo,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
tui = SplitTUI(config=config)
|
|
456
|
+
tui.run()
|