dbagent-cli 0.5.2__tar.gz → 0.6.1__tar.gz
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.
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/PKG-INFO +1 -1
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/__init__.py +1 -1
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/cli.py +102 -38
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/llm/gemini_provider.py +4 -5
- dbagent_cli-0.6.1/dbagent/ui/chat_screen.py +211 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent_cli.egg-info/PKG-INFO +1 -1
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent_cli.egg-info/SOURCES.txt +1 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/pyproject.toml +1 -1
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/README.md +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/agent/confidence.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/agent/conversation.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/agent/doctor.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/agent/error_classifier.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/agent/generator.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/agent/pipeline.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/agent/recommender.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/agent/validator.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/config.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/connectors/base.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/connectors/factory.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/connectors/mongo.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/connectors/relational.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/execution/__init__.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/execution/cache.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/execution/changeset.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/execution/cost.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/execution/recovery.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/execution/sandbox.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/execution/streaming.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/llm/auto_setup.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/llm/base.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/llm/factory.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/llm/groq_provider.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/llm/mock_provider.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/llm/ollama_provider.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/llm/openrouter_provider.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/schema/cache.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/schema/formatter.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/schema/graph.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/schema/models.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/schema/retriever.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/schema/selector.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/security/__init__.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/security/audit.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/security/environment.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/security/masking.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/security/modes.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/security/rbac.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/semantic/__init__.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/semantic/dictionary.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/semantic/templates.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/sql/__init__.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/sql/optimizer.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/sql/validator.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/ui/console.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/ui/grid_window.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/ui/viewer.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent/ui/visualizer.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent_cli.egg-info/dependency_links.txt +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent_cli.egg-info/entry_points.txt +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent_cli.egg-info/requires.txt +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/dbagent_cli.egg-info/top_level.txt +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/setup.cfg +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_cache.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_cli.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_connectors.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_doctor_and_viz.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_generator.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_graph.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_recovery.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_safety.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_sandbox.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_schema.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_security.py +0 -0
- {dbagent_cli-0.5.2 → dbagent_cli-0.6.1}/tests/test_semantic.py +0 -0
|
@@ -14,9 +14,10 @@ import typer
|
|
|
14
14
|
from rich.prompt import Prompt, Confirm
|
|
15
15
|
from rich.table import Table
|
|
16
16
|
from rich.markdown import Markdown
|
|
17
|
-
from
|
|
18
|
-
from prompt_toolkit import PromptSession
|
|
17
|
+
from prompt_toolkit import PromptSession, HTML
|
|
19
18
|
from prompt_toolkit.history import FileHistory
|
|
19
|
+
from prompt_toolkit.completion import WordCompleter
|
|
20
|
+
from prompt_toolkit.styles import Style
|
|
20
21
|
|
|
21
22
|
from dbagent import __version__, __author__
|
|
22
23
|
from dbagent.config import ConfigManager
|
|
@@ -63,6 +64,13 @@ from dbagent.ui.console import (
|
|
|
63
64
|
)
|
|
64
65
|
from dbagent.ui.viewer import SchemaViewer
|
|
65
66
|
from dbagent.ui.grid_window import open_desktop_grid_window, open_browser_grid_window
|
|
67
|
+
from dbagent.ui.chat_screen import (
|
|
68
|
+
clear_screen,
|
|
69
|
+
render_chat_header,
|
|
70
|
+
render_user_turn,
|
|
71
|
+
render_assistant_turn,
|
|
72
|
+
render_session_history,
|
|
73
|
+
)
|
|
66
74
|
|
|
67
75
|
app = typer.Typer(
|
|
68
76
|
name="db-agent",
|
|
@@ -447,27 +455,38 @@ def chat_command(
|
|
|
447
455
|
dialect = connector.engine.dialect.name
|
|
448
456
|
current_mode = OperationMode(mode.upper()) if mode.upper() in OperationMode.__members__ else OperationMode.CONFIRM
|
|
449
457
|
|
|
450
|
-
console.print(f"[bold green]Connected:[/bold green] {db_name} ({dialect}) - {len(table_names)} tables")
|
|
451
|
-
console.print(f"[bold cyan]AI:[/bold cyan] {llm.name} | [bold yellow]Mode:[/bold yellow] {current_mode.value}")
|
|
452
|
-
console.print("[dim]Type your question in plain English or use colon commands (e.g. :help, :tables, :doctor).[/dim]")
|
|
453
|
-
|
|
454
458
|
# Display AI recommendations on connect (instant <0.1ms with zero network overhead)
|
|
459
|
+
recs = []
|
|
455
460
|
try:
|
|
456
461
|
cached_schema = cache_mgr.load_cached_schema(db_url)
|
|
457
462
|
if cached_schema:
|
|
458
463
|
recs = QueryRecommender.generate_recommendations(cached_schema, max_suggestions=3)
|
|
459
464
|
else:
|
|
460
465
|
recs = QueryRecommender.generate_from_table_names(table_names, max_suggestions=3)
|
|
461
|
-
if recs:
|
|
462
|
-
console.print("\n[bold cyan]💡 Try asking:[/bold cyan]")
|
|
463
|
-
for r in recs:
|
|
464
|
-
console.print(f" → [italic]{r}[/italic]")
|
|
465
466
|
except Exception:
|
|
466
467
|
pass
|
|
467
|
-
|
|
468
|
+
|
|
469
|
+
# Clear screen and render Claude-style clean header card
|
|
470
|
+
clear_screen()
|
|
471
|
+
render_chat_header(
|
|
472
|
+
db_name=db_name,
|
|
473
|
+
dialect=dialect,
|
|
474
|
+
table_count=len(table_names),
|
|
475
|
+
ai_provider=llm.name,
|
|
476
|
+
ai_model=model or getattr(llm, "default_model", None),
|
|
477
|
+
mode=current_mode.value,
|
|
478
|
+
recommendations=recs,
|
|
479
|
+
)
|
|
468
480
|
|
|
469
481
|
history_file = config_mgr.config_dir / "chat_history.txt"
|
|
470
|
-
|
|
482
|
+
chat_completer = WordCompleter([
|
|
483
|
+
":help", ":tables", ":schema", ":dt", ":table", ":fk", ":path", ":join",
|
|
484
|
+
":doctor", ":mode", ":changes", ":provider", ":model", ":models",
|
|
485
|
+
":page", ":card", ":wide", ":scroll", ":window", ":grid", ":web",
|
|
486
|
+
":explain", ":history", ":auto", ":noauto", ":run", ":clear", ":export",
|
|
487
|
+
":exit", ":quit", ":q",
|
|
488
|
+
], ignore_case=True)
|
|
489
|
+
session = PromptSession(history=FileHistory(str(history_file)), completer=chat_completer)
|
|
471
490
|
|
|
472
491
|
auto_execute = True
|
|
473
492
|
conversation_ctx = ConversationContext()
|
|
@@ -488,7 +507,7 @@ def chat_command(
|
|
|
488
507
|
|
|
489
508
|
while True:
|
|
490
509
|
try:
|
|
491
|
-
user_input = session.prompt("db-agent> ").strip()
|
|
510
|
+
user_input = session.prompt(HTML("<ansicyan><b>db-agent</b></ansicyan> <ansiblue><b>❯</b></ansiblue> ")).strip()
|
|
492
511
|
if not user_input:
|
|
493
512
|
continue
|
|
494
513
|
|
|
@@ -512,9 +531,9 @@ def chat_command(
|
|
|
512
531
|
console.print(" [bold]:doctor[/bold] -> Run database health diagnostics")
|
|
513
532
|
console.print(" [bold]:mode <name>[/bold] -> Switch mode (SAFE, READ_ONLY, CONFIRM, SANDBOX, ADMIN)")
|
|
514
533
|
console.print(" [bold]:changes[/bold] -> List recent write ChangeSets")
|
|
515
|
-
console.print(" [bold]:provider <name>[/bold] -> Switch AI provider (
|
|
516
|
-
console.print(" [bold]:
|
|
517
|
-
console.print(" [bold]:
|
|
534
|
+
console.print(" [bold]:provider <name>[/bold] -> Switch AI provider (gemini, ollama, groq, openrouter)")
|
|
535
|
+
console.print(" [bold]:models[/bold] -> View list of available AI models & speed benchmarks")
|
|
536
|
+
console.print(" [bold]:model <num|name>[/bold] -> Switch active model (:model 1, :model 3.7, :model 3.6)")
|
|
518
537
|
console.print(" [bold]:page <n>[/bold] -> View column page <n> for wide multi-column tables")
|
|
519
538
|
console.print(" [bold]:card[/bold] -> View last result in expanded vertical card format (all columns)")
|
|
520
539
|
console.print(" [bold]:wide[/bold] -> Force-show all columns in terminal (zoom out)")
|
|
@@ -680,21 +699,53 @@ def chat_command(
|
|
|
680
699
|
print_error(f"Unknown provider '{cmd_arg}'. Supported: ollama, gemini, groq, openrouter")
|
|
681
700
|
continue
|
|
682
701
|
|
|
683
|
-
if cmd_name
|
|
684
|
-
if
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
702
|
+
if cmd_name in [":model", ":models", ":providers", ":ai"]:
|
|
703
|
+
avail_models = llm.list_models() if hasattr(llm, "list_models") else []
|
|
704
|
+
curr_model = model or getattr(llm, "default_model", "default")
|
|
705
|
+
|
|
706
|
+
# If no arg passed or :models/:providers called, show rich model selection menu
|
|
707
|
+
if not cmd_arg or cmd_name in [":models", ":providers", ":ai"]:
|
|
708
|
+
console.print(f"\n[bold cyan]Active AI Provider:[/bold cyan] [bold white]{llm.name}[/bold white]")
|
|
709
|
+
console.print(f"[bold cyan]Active Model:[/bold cyan] [bold green]{curr_model}[/bold green]\n")
|
|
710
|
+
if avail_models:
|
|
711
|
+
console.print(f"[bold]Available Models for {llm.name}:[/bold]")
|
|
712
|
+
model_descriptions = {
|
|
713
|
+
"gemini-flash-lite-latest": "⚡ Ultra-Fast (~0.8s) • Recommended for instant chat & queries",
|
|
714
|
+
"gemini-3.6-flash": "⚡ High Quality (~3.5s) • Strong SQL reasoning & accuracy",
|
|
715
|
+
"gemini-3.7-flash": "🧠 Deep Reasoning (~13s) • Latest flagship model",
|
|
716
|
+
"gemini-3.1-flash-lite": "⚡ Fast Lite (~4.0s) • Low latency",
|
|
717
|
+
"gemini-flash-latest": "⚡ Standard Flash (~7.0s)",
|
|
718
|
+
}
|
|
719
|
+
for idx, m_name in enumerate(avail_models, 1):
|
|
720
|
+
is_active = " [bold green]✓ Active[/bold green]" if m_name == curr_model else ""
|
|
721
|
+
desc = model_descriptions.get(m_name, "")
|
|
722
|
+
desc_str = f" - [dim]{desc}[/dim]" if desc else ""
|
|
723
|
+
console.print(f" [bold cyan]{idx}[/bold cyan]. [bold white]{m_name}[/bold white]{desc_str}{is_active}")
|
|
724
|
+
console.print("\n[dim]Quick Switch: [bold]:model 1[/bold] (ultra-fast) | [bold]:model 3.7[/bold] | [bold]:model 3.6[/bold] | [bold]:model <name>[/bold][/dim]\n")
|
|
688
725
|
continue
|
|
689
|
-
model = cmd_arg
|
|
690
|
-
print_success(f"Switched active model to [bold cyan]{model}[/bold cyan] (Provider: {llm.name})")
|
|
691
|
-
continue
|
|
692
726
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
727
|
+
# Handle numeric shortcut or name shortcut
|
|
728
|
+
target_model = cmd_arg.strip()
|
|
729
|
+
if target_model.isdigit() and avail_models:
|
|
730
|
+
choice_idx = int(target_model) - 1
|
|
731
|
+
if 0 <= choice_idx < len(avail_models):
|
|
732
|
+
target_model = avail_models[choice_idx]
|
|
733
|
+
elif target_model in ["3.7", "37"]:
|
|
734
|
+
target_model = "gemini-3.7-flash"
|
|
735
|
+
elif target_model in ["3.6", "36"]:
|
|
736
|
+
target_model = "gemini-3.6-flash"
|
|
737
|
+
elif target_model in ["lite", "fast", "flash-lite", "1"]:
|
|
738
|
+
target_model = "gemini-flash-lite-latest"
|
|
739
|
+
elif target_model in ["3.1"]:
|
|
740
|
+
target_model = "gemini-3.1-flash-lite"
|
|
741
|
+
elif avail_models:
|
|
742
|
+
matches = [m for m in avail_models if target_model.lower() in m.lower()]
|
|
743
|
+
if matches:
|
|
744
|
+
target_model = matches[0]
|
|
745
|
+
|
|
746
|
+
model = target_model
|
|
747
|
+
pipeline.model = model
|
|
748
|
+
print_success(f"Switched active model to [bold cyan]{model}[/bold cyan] (Provider: {llm.name})")
|
|
698
749
|
continue
|
|
699
750
|
|
|
700
751
|
if cmd_name == ":explain":
|
|
@@ -710,14 +761,20 @@ def chat_command(
|
|
|
710
761
|
continue
|
|
711
762
|
|
|
712
763
|
if cmd_name == ":history":
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
764
|
+
render_session_history(query_history)
|
|
765
|
+
continue
|
|
766
|
+
|
|
767
|
+
if cmd_name in [":clear", ":cls"]:
|
|
768
|
+
clear_screen()
|
|
769
|
+
render_chat_header(
|
|
770
|
+
db_name=db_name,
|
|
771
|
+
dialect=dialect,
|
|
772
|
+
table_count=len(table_names),
|
|
773
|
+
ai_provider=llm.name,
|
|
774
|
+
ai_model=model or getattr(llm, "default_model", None),
|
|
775
|
+
mode=current_mode.value,
|
|
776
|
+
recommendations=recs,
|
|
777
|
+
)
|
|
721
778
|
continue
|
|
722
779
|
|
|
723
780
|
if cmd_name in [":page", ":cols", ":p"]:
|
|
@@ -817,10 +874,17 @@ def chat_command(
|
|
|
817
874
|
pipeline.mode = current_mode
|
|
818
875
|
pipeline.auto_execute = auto_execute
|
|
819
876
|
|
|
877
|
+
render_user_turn(user_input)
|
|
878
|
+
|
|
820
879
|
with DynamicSpinner():
|
|
821
880
|
result = pipeline.run(user_prompt=user_input)
|
|
822
881
|
|
|
823
|
-
|
|
882
|
+
render_assistant_turn(
|
|
883
|
+
result,
|
|
884
|
+
show_sql=True,
|
|
885
|
+
ai_provider=llm.name,
|
|
886
|
+
ai_model=model or getattr(llm, "default_model", None),
|
|
887
|
+
)
|
|
824
888
|
|
|
825
889
|
last_result = result
|
|
826
890
|
query_history.append(result)
|
|
@@ -14,19 +14,18 @@ class GeminiProvider(BaseLLMProvider):
|
|
|
14
14
|
"""Google Gemini LLM provider (Free Tier available)."""
|
|
15
15
|
|
|
16
16
|
FALLBACK_MODELS = [
|
|
17
|
+
"gemini-flash-lite-latest",
|
|
17
18
|
"gemini-3.6-flash",
|
|
18
19
|
"gemini-3.7-flash",
|
|
19
|
-
"gemini-3.
|
|
20
|
+
"gemini-3.1-flash-lite",
|
|
20
21
|
"gemini-flash-latest",
|
|
21
|
-
"gemini-pro-latest",
|
|
22
|
-
"gemini-2.5-flash",
|
|
23
22
|
]
|
|
24
23
|
|
|
25
24
|
def __init__(self, api_key: Optional[str] = None, default_model: str = "gemini-3.6-flash"):
|
|
26
25
|
self.api_key = api_key or os.getenv("GEMINI_API_KEY")
|
|
27
26
|
self.default_model = default_model
|
|
28
27
|
self._session = requests.Session()
|
|
29
|
-
self._session.headers.update({"User-Agent": "DB-Agent/0.
|
|
28
|
+
self._session.headers.update({"User-Agent": "DB-Agent/0.6.0"})
|
|
30
29
|
|
|
31
30
|
@property
|
|
32
31
|
def name(self) -> str:
|
|
@@ -117,7 +116,7 @@ class GeminiProvider(BaseLLMProvider):
|
|
|
117
116
|
else:
|
|
118
117
|
raise RuntimeError(f"Gemini API error ({response.status_code}): {response.text}")
|
|
119
118
|
else:
|
|
120
|
-
response = self._session.post(endpoint, json=payload, timeout=
|
|
119
|
+
response = self._session.post(endpoint, json=payload, timeout=15)
|
|
121
120
|
if response.status_code == 200:
|
|
122
121
|
data = response.json()
|
|
123
122
|
candidates = data.get("candidates", [])
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Claude-Style Clean Chat Screen UI Component for DB-Agent.
|
|
3
|
+
Renders clean status headers, message turn cards, visual history tables, and styled prompt layouts.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import List, Optional, Dict, Any
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
from rich.markdown import Markdown
|
|
13
|
+
from rich.syntax import Syntax
|
|
14
|
+
from rich.rule import Rule
|
|
15
|
+
|
|
16
|
+
from dbagent import __version__
|
|
17
|
+
from dbagent.agent.pipeline import PipelineResult
|
|
18
|
+
from dbagent.ui.console import print_results_table, print_error, print_warning, print_info
|
|
19
|
+
from dbagent.ui.visualizer import ResultVisualizer
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def clear_screen() -> None:
|
|
25
|
+
"""Clear terminal screen cleanly across Windows, macOS, and Linux."""
|
|
26
|
+
os.system("cls" if os.name == "nt" else "clear")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def render_chat_header(
|
|
30
|
+
db_name: str,
|
|
31
|
+
dialect: str,
|
|
32
|
+
table_count: int,
|
|
33
|
+
ai_provider: str,
|
|
34
|
+
ai_model: Optional[str] = None,
|
|
35
|
+
mode: str = "CONFIRM",
|
|
36
|
+
recommendations: Optional[List[str]] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Render the top Claude-style status header card."""
|
|
39
|
+
model_str = f" ({ai_model})" if ai_model else ""
|
|
40
|
+
|
|
41
|
+
header_content = Text()
|
|
42
|
+
header_content.append("⚡ DB-AGENT ", style="bold cyan")
|
|
43
|
+
header_content.append(f"v{__version__}\n", style="bold green")
|
|
44
|
+
|
|
45
|
+
header_content.append("🟢 Connected: ", style="bold green")
|
|
46
|
+
header_content.append(f"{db_name} ", style="bold white")
|
|
47
|
+
header_content.append(f"({dialect}) • {table_count} tables\n", style="dim")
|
|
48
|
+
|
|
49
|
+
header_content.append("🤖 AI: ", style="bold cyan")
|
|
50
|
+
header_content.append(f"{ai_provider}{model_str}", style="bold white")
|
|
51
|
+
header_content.append(" | ", style="dim")
|
|
52
|
+
header_content.append("🛡️ Mode: ", style="bold yellow")
|
|
53
|
+
header_content.append(f"{mode}", style="bold yellow")
|
|
54
|
+
header_content.append(" | ", style="dim")
|
|
55
|
+
header_content.append("⌨️ Type ", style="dim")
|
|
56
|
+
header_content.append(":help", style="bold cyan")
|
|
57
|
+
header_content.append(" for commands", style="dim")
|
|
58
|
+
|
|
59
|
+
panel = Panel(
|
|
60
|
+
header_content,
|
|
61
|
+
border_style="cyan",
|
|
62
|
+
padding=(0, 1),
|
|
63
|
+
)
|
|
64
|
+
console.print(panel)
|
|
65
|
+
|
|
66
|
+
if recommendations:
|
|
67
|
+
console.print("[dim italic]💡 Try asking:[/dim italic]")
|
|
68
|
+
for rec in recommendations[:3]:
|
|
69
|
+
console.print(f" [dim]→[/dim] [cyan]{rec}[/cyan]")
|
|
70
|
+
console.print("")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def render_user_turn(user_prompt: str) -> None:
|
|
74
|
+
"""Render a user turn message bubble/card."""
|
|
75
|
+
user_text = Text()
|
|
76
|
+
user_text.append("💬 You: ", style="bold blue")
|
|
77
|
+
user_text.append(user_prompt, style="bold white")
|
|
78
|
+
console.print("")
|
|
79
|
+
console.print(Rule(style="dim blue"))
|
|
80
|
+
console.print(user_text)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def render_assistant_turn(
|
|
84
|
+
result: PipelineResult,
|
|
85
|
+
show_sql: bool = True,
|
|
86
|
+
ai_provider: str = "gemini",
|
|
87
|
+
ai_model: Optional[str] = None,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Render the assistant response card with SQL, data grid, and execution timing."""
|
|
90
|
+
model_display = f" ({ai_model})" if ai_model else ""
|
|
91
|
+
timing_str = f"⚡ {result.execution_time_ms / 1000:.1f}s" if result.execution_time_ms > 0 else ""
|
|
92
|
+
|
|
93
|
+
# Conversational Chat Response
|
|
94
|
+
if result.is_chat and result.message:
|
|
95
|
+
console.print(f"\n[bold cyan]🤖 DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]")
|
|
96
|
+
console.print(Markdown(result.message))
|
|
97
|
+
console.print("")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# Error Response
|
|
101
|
+
if result.error:
|
|
102
|
+
console.print(f"\n[bold red]🤖 DB-Agent (Error)[/bold red] [dim]{timing_str}[/dim]")
|
|
103
|
+
if result.friendly_error:
|
|
104
|
+
print_error(result.friendly_error)
|
|
105
|
+
else:
|
|
106
|
+
print_error(f"Error: {result.error}")
|
|
107
|
+
|
|
108
|
+
if result.error_suggestions:
|
|
109
|
+
console.print(f"[dim]Did you mean: {', '.join(result.error_suggestions)}[/dim]")
|
|
110
|
+
|
|
111
|
+
if result.sql:
|
|
112
|
+
console.print("[dim]Generated SQL:[/dim]")
|
|
113
|
+
syntax = Syntax(result.sql.strip(), "sql", theme="monokai", line_numbers=False)
|
|
114
|
+
console.print(Panel(syntax, border_style="red", padding=(0, 1)))
|
|
115
|
+
if result.retries > 0:
|
|
116
|
+
console.print(f"[dim](Retried {result.retries} time(s))[/dim]")
|
|
117
|
+
console.print("")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
# Executed Result Response
|
|
121
|
+
if result.was_executed:
|
|
122
|
+
console.print(f"\n[bold cyan]🤖 DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]")
|
|
123
|
+
|
|
124
|
+
if show_sql and result.sql:
|
|
125
|
+
sql_clean = result.sql.strip()
|
|
126
|
+
syntax = Syntax(sql_clean, "sql", theme="monokai", line_numbers=False)
|
|
127
|
+
console.print(Panel(syntax, title="[dim]SQL Statement[/dim]", border_style="dim", padding=(0, 1)))
|
|
128
|
+
if result.is_cached:
|
|
129
|
+
console.print(f"[dim italic]⚡ Cached result ({round(result.cache_age_seconds, 1)}s old)[/dim italic]")
|
|
130
|
+
|
|
131
|
+
if result.has_results:
|
|
132
|
+
print_results_table(result.columns, result.rows, title="Results")
|
|
133
|
+
|
|
134
|
+
# ASCII visualization if detected
|
|
135
|
+
if result.visualization and result.visualization.chart_type == "BAR":
|
|
136
|
+
console.print("")
|
|
137
|
+
bar_chart = ResultVisualizer.render_ascii_bar_chart(
|
|
138
|
+
result.visualization.x_column,
|
|
139
|
+
result.visualization.y_column,
|
|
140
|
+
result.rows,
|
|
141
|
+
)
|
|
142
|
+
console.print(bar_chart)
|
|
143
|
+
console.print("")
|
|
144
|
+
else:
|
|
145
|
+
print_info("Query executed successfully. No rows returned.")
|
|
146
|
+
|
|
147
|
+
if result.confidence:
|
|
148
|
+
console.print(result.confidence.format_summary())
|
|
149
|
+
|
|
150
|
+
if result.retries > 0:
|
|
151
|
+
console.print(f"[dim](Auto-corrected after {result.retries} retry(ies))[/dim]")
|
|
152
|
+
|
|
153
|
+
# Recovery details for writes
|
|
154
|
+
if result.change_id:
|
|
155
|
+
console.print(f"\n[bold green]✓ Change committed successfully[/bold green]")
|
|
156
|
+
console.print(f" * Change ID: [bold white]{result.change_id}[/bold white]")
|
|
157
|
+
if result.recovery_status == "AVAILABLE":
|
|
158
|
+
console.print(f" * Recovery: [bold cyan]{result.recovery_message}[/bold cyan]")
|
|
159
|
+
console.print(f" * Rollback command: [bold]db-agent rollback {result.change_id}[/bold]\n")
|
|
160
|
+
else:
|
|
161
|
+
console.print(f" * Recovery: [yellow]{result.recovery_message or result.recovery_status}[/yellow]\n")
|
|
162
|
+
console.print("")
|
|
163
|
+
elif result.needs_confirmation and not result.was_executed:
|
|
164
|
+
if result.sql:
|
|
165
|
+
syntax = Syntax(result.sql.strip(), "sql", theme="monokai", line_numbers=False)
|
|
166
|
+
console.print(Panel(syntax, title=f"Generated {result.query_type.upper()} Query", border_style="yellow"))
|
|
167
|
+
console.print("[dim]This query was not executed (requires confirmation).[/dim]\n")
|
|
168
|
+
elif result.sql:
|
|
169
|
+
syntax = Syntax(result.sql.strip(), "sql", theme="monokai", line_numbers=False)
|
|
170
|
+
console.print(Panel(syntax, title="Generated SQL", border_style="dim"))
|
|
171
|
+
console.print("")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def render_session_history(query_history: List[PipelineResult]) -> None:
|
|
175
|
+
"""Render a clean visual session history table."""
|
|
176
|
+
if not query_history:
|
|
177
|
+
console.print("[dim]No query history in this session yet.[/dim]\n")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
table = Table(title="📜 Session Query History", border_style="cyan", show_lines=True)
|
|
181
|
+
table.add_column("#", style="dim", width=4, justify="right")
|
|
182
|
+
table.add_column("Status", width=8, justify="center")
|
|
183
|
+
table.add_column("Type", width=8, justify="center")
|
|
184
|
+
table.add_column("Query / Message Preview", style="white", min_width=30)
|
|
185
|
+
table.add_column("Rows", style="cyan", width=6, justify="right")
|
|
186
|
+
table.add_column("Time", style="green", width=8, justify="right")
|
|
187
|
+
|
|
188
|
+
for i, r in enumerate(query_history, 1):
|
|
189
|
+
if r.is_chat:
|
|
190
|
+
status = "[bold blue]CHAT[/bold blue]"
|
|
191
|
+
q_type = "chat"
|
|
192
|
+
preview = (r.message or "")[:60] + "..." if len(r.message or "") > 60 else (r.message or "")
|
|
193
|
+
rows = "-"
|
|
194
|
+
elif r.success:
|
|
195
|
+
status = "[bold green]OK[/bold green]"
|
|
196
|
+
q_type = r.query_type
|
|
197
|
+
sql_str = r.sql or ""
|
|
198
|
+
preview = (sql_str[:60] + "...") if len(sql_str) > 60 else sql_str
|
|
199
|
+
rows = str(len(r.rows)) if r.has_results else "0"
|
|
200
|
+
else:
|
|
201
|
+
status = "[bold red]FAIL[/bold red]"
|
|
202
|
+
q_type = r.query_type or "error"
|
|
203
|
+
preview = (r.error or r.sql or "")[:60]
|
|
204
|
+
rows = "0"
|
|
205
|
+
|
|
206
|
+
time_str = f"{r.execution_time_ms / 1000:.1f}s" if r.execution_time_ms > 0 else "-"
|
|
207
|
+
table.add_row(str(i), status, q_type, preview, rows, time_str)
|
|
208
|
+
|
|
209
|
+
console.print("")
|
|
210
|
+
console.print(table)
|
|
211
|
+
console.print("")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dbagent-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.1"
|
|
8
8
|
description = "Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|