rdai 1.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.
rdai/__init__.py ADDED
@@ -0,0 +1,182 @@
1
+ """Public SDK interface for rdai.
2
+
3
+ The :class:`AI` facade deliberately keeps application code independent from a
4
+ specific model vendor. It discovers local credentials, constructs provider
5
+ adapters at the package boundary, and delegates all selection and retry policy
6
+ to the provider-agnostic core.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import Iterable, Mapping, Sequence
12
+ from pathlib import Path
13
+ from typing import Any, Final
14
+
15
+ from .config.discovery import discover_api_keys, normalize_provider_name
16
+ from .config.loader import RdaiConfig, load_config
17
+ from .core.engine import Failover, ProviderRegistry, Router
18
+
19
+ from .providers.base import BaseProvider
20
+ from .providers.gemini import GeminiProvider
21
+ from .providers.groq import GroqProvider
22
+ from .providers.openai import OpenAIProvider
23
+ from .providers.vertexai import VertexaiProvider
24
+ from .providers.claude import ClaudeProvider
25
+ from .providers.aws_bedrock import AwsBedrockProvider
26
+ from .providers.deepseek import DeepseekProvider
27
+ from .providers.qwen import QwenProvider
28
+ from .providers.llama import LlamaProvider
29
+ from .providers.mistral import MistralProvider
30
+ from .providers.huggingface import HuggingfaceProvider
31
+
32
+ __version__ = "1.0.0"
33
+
34
+ _BUILTIN_PROVIDER_CLASSES: Final[dict[str, type[BaseProvider]]] = {
35
+ "gemini": GeminiProvider,
36
+ "groq": GroqProvider,
37
+ "openai": OpenAIProvider,
38
+ "vertexai": VertexaiProvider,
39
+ "claude": ClaudeProvider,
40
+ "aws_bedrock": AwsBedrockProvider,
41
+ "deepseek": DeepseekProvider,
42
+ "qwen": QwenProvider,
43
+ "llama": LlamaProvider,
44
+ "mistral": MistralProvider,
45
+ "huggingface": HuggingfaceProvider,
46
+ }
47
+ _DEFAULT_PROVIDER_ORDER: Final[tuple[str, ...]] = tuple(_BUILTIN_PROVIDER_CLASSES)
48
+
49
+
50
+ class AI:
51
+ """A resilient, provider-agnostic AI generation client.
52
+
53
+ Args:
54
+ strategy: ``"smart"`` chooses a provider based on prompt/trait fit;
55
+ ``"manual"`` follows the configured provider order. When omitted,
56
+ the value in ``rdai.yaml`` is used.
57
+ config_path: Optional path to an ``rdai.yaml`` routing-policy file.
58
+ env_file: Optional path to a dotenv file holding provider credentials.
59
+ By default, rdai checks the dotenv file beside ``config_path``.
60
+ providers: Optional provider instances. This is useful for custom
61
+ adapters and tests; injected providers are registered without the
62
+ core ever knowing their concrete type.
63
+ api_keys: Optional provider-name to API-key mapping. These values take
64
+ precedence over automatic environment discovery.
65
+ models: Optional provider-name to model-name mapping for built-in
66
+ providers.
67
+ failure_threshold: Number of consecutive transient failures before a
68
+ provider circuit opens.
69
+ recovery_timeout: Seconds an open circuit stays unavailable before a
70
+ probe is allowed again.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ *,
76
+ strategy: str | None = None,
77
+ config_path: str | Path | None = None,
78
+ env_file: str | Path | None = None,
79
+ providers: Iterable[BaseProvider] | None = None,
80
+ api_keys: Mapping[str, str] | None = None,
81
+ models: Mapping[str, str] | None = None,
82
+ failure_threshold: int = 3,
83
+ recovery_timeout: float = 30.0,
84
+ ) -> None:
85
+ config_file = (
86
+ Path(config_path).expanduser().resolve()
87
+ if config_path is not None
88
+ else (Path.cwd() / "rdai.yaml").resolve()
89
+ )
90
+ effective_env_file = (
91
+ Path(env_file).expanduser().resolve()
92
+ if env_file is not None
93
+ else config_file.parent / ".env"
94
+ )
95
+
96
+ self.config: RdaiConfig = load_config(config_file, env_file=effective_env_file)
97
+ self.strategy = strategy if strategy is not None else self.config.strategy
98
+ self.registry = ProviderRegistry()
99
+
100
+ if providers is None:
101
+ self._register_builtin_providers(
102
+ api_keys=self._resolved_api_keys(effective_env_file, api_keys),
103
+ models=models,
104
+ )
105
+ else:
106
+ for provider in providers:
107
+ self.registry.register(provider)
108
+
109
+ configured_order = self.config.provider_order or _DEFAULT_PROVIDER_ORDER
110
+ self.router = Router(
111
+ self.registry,
112
+ strategy=self.strategy,
113
+ priority=configured_order,
114
+ )
115
+ self.failover = Failover(
116
+ self.router,
117
+ failure_threshold=failure_threshold,
118
+ recovery_timeout=recovery_timeout,
119
+ )
120
+
121
+ @staticmethod
122
+ def _resolved_api_keys(
123
+ env_file: Path,
124
+ explicit_api_keys: Mapping[str, str] | None,
125
+ ) -> dict[str, str]:
126
+ """Merge discovered and explicitly supplied keys without leaking them."""
127
+
128
+ discovered = discover_api_keys(env_file)
129
+ if explicit_api_keys is None:
130
+ return discovered
131
+
132
+ for provider, key in explicit_api_keys.items():
133
+ if not isinstance(key, str) or not key.strip():
134
+ continue
135
+ discovered[normalize_provider_name(provider)] = key.strip()
136
+ return discovered
137
+
138
+ def _register_builtin_providers(
139
+ self,
140
+ *,
141
+ api_keys: Mapping[str, str],
142
+ models: Mapping[str, str] | None,
143
+ ) -> None:
144
+ """Register built-in adapters at the package edge.
145
+
146
+ Instantiating an adapter without a credential is intentional. It gives
147
+ routing one stable registry while each adapter's ``is_available`` flag
148
+ prevents an unconfigured provider from ever receiving a request.
149
+ """
150
+
151
+ selected = self.config.provider_order or _DEFAULT_PROVIDER_ORDER
152
+ normalized_models = {
153
+ normalize_provider_name(provider): model
154
+ for provider, model in (models or {}).items()
155
+ if isinstance(model, str) and model.strip()
156
+ }
157
+
158
+ for provider_name in selected:
159
+ provider_class = _BUILTIN_PROVIDER_CLASSES.get(provider_name)
160
+ if provider_class is None:
161
+ continue
162
+ provider = provider_class(
163
+ api_key=api_keys.get(provider_name),
164
+ model=normalized_models.get(provider_name),
165
+ )
166
+ self.registry.register(provider)
167
+
168
+ def generate(
169
+ self,
170
+ prompt: str,
171
+ *,
172
+ traits: Sequence[str] | None = None,
173
+ **kwargs: Any,
174
+ ) -> str:
175
+ """Generate text through the selected provider with automatic failover."""
176
+
177
+ if not isinstance(prompt, str) or not prompt.strip():
178
+ raise ValueError("prompt must be a non-empty string.")
179
+ return self.failover.generate(prompt, traits=traits, **kwargs)
180
+
181
+
182
+ __all__ = ["AI", "__version__"]
File without changes
@@ -0,0 +1,34 @@
1
+ import typer
2
+ from rich.console import Console
3
+ from rich.panel import Panel
4
+ from rich.align import Align
5
+
6
+ console = Console()
7
+
8
+ def print_mini_header():
9
+ header = (
10
+ "[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
11
+ "[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
12
+ "[dim]────────────────────────────────────────────────────────────[/dim]"
13
+ )
14
+ console.print(Align.center(header))
15
+ console.print()
16
+
17
+ def run_about():
18
+ """Learn about the rdai orchestrator and architecture."""
19
+ console.print()
20
+ print_mini_header()
21
+
22
+ about_text = (
23
+ "[bold white]The Quantum-Ready, Self-Healing, Multi-Brain AI OS[/bold white]\n\n"
24
+ "[cyan]rdai[/cyan] is not just a routing tool; it is a master orchestration engine.\n"
25
+ "Designed to eliminate AI downtime, it features a limitless circuit breaker\n"
26
+ "mechanism that silently auto-failovers across any number of AI providers.\n\n"
27
+ "[bold green]Key Features:[/bold green]\n"
28
+ " 🧠 [yellow]Limitless Multi-Brain Orchestration[/yellow]\n"
29
+ " ⚡ [yellow]Zero-Downtime Smart Auto-Routing[/yellow]\n"
30
+ " 🛠️ [yellow]Bring-Your-Own-Model (BYOM) Ready[/yellow]"
31
+ )
32
+
33
+ console.print(Panel(Align.center(about_text), border_style="cyan"))
34
+ console.print()
@@ -0,0 +1,67 @@
1
+ import time
2
+ import typer
3
+ from rich.console import Console
4
+ from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
5
+ from rich.align import Align # 👈 Added this missing import!
6
+ from rdai.config.discovery import discover_api_keys
7
+
8
+ console = Console()
9
+
10
+ def print_mini_header():
11
+ header = (
12
+ "[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
13
+ "[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
14
+ "[dim]────────────────────────────────────────────────────────────[/dim]"
15
+ )
16
+ console.print(Align.center(header))
17
+ console.print()
18
+
19
+ def run_benchmark():
20
+ """Run a real latency test on configured models."""
21
+ print_mini_header() # 👈 Called the header function here!
22
+
23
+ console.print("\n[bold cyan]⚡ Initiating REAL System Benchmark...[/bold cyan]\n")
24
+
25
+ # 🛑 1. Check for real keys first! No more fake simulation!
26
+ keys = discover_api_keys()
27
+
28
+ if not keys:
29
+ console.print("[red]🚨 ALERT: No API keys detected in .env file![/red]")
30
+ console.print("[yellow]Please run 'rdai init' and add real keys before running a benchmark.[/yellow]\n")
31
+ raise typer.Exit()
32
+
33
+ console.print(f"[yellow]🔍 Found active keys for: {', '.join(keys.keys()).title()}[/yellow]\n")
34
+
35
+ # 2. Dynamic import to avoid circular dependencies
36
+ from rdai import AI
37
+
38
+ # ⏳ 3. Real Progress and Real Pinging
39
+ with Progress(
40
+ SpinnerColumn(),
41
+ TextColumn("[progress.description]{task.description}"),
42
+ BarColumn(),
43
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
44
+ transient=False,
45
+ ) as progress:
46
+
47
+ task = progress.add_task("[cyan]Pinging real networks...[/cyan]", total=len(keys))
48
+
49
+ for provider_name, api_key in keys.items():
50
+ try:
51
+ # We inject only one provider at a time to test its true latency
52
+ tester = AI(strategy="manual", providers=[provider_name])
53
+
54
+ start_time = time.time()
55
+ # 📡 REAL API CALL!
56
+ tester.generate("Reply with a single word: OK.")
57
+ end_time = time.time()
58
+
59
+ latency = int((end_time - start_time) * 1000)
60
+ console.print(f" [green]✔ {provider_name.capitalize()} actually responded in {latency}ms[/green]")
61
+
62
+ except Exception as e:
63
+ console.print(f" [red]✖ {provider_name.capitalize()} failed to respond. (Invalid Key or Timeout)[/red]")
64
+
65
+ progress.advance(task)
66
+
67
+ console.print("\n[bold green]✅ Real Benchmark Complete![/bold green]\n")
@@ -0,0 +1,39 @@
1
+ import typer
2
+ import yaml
3
+ import os
4
+ from rich.console import Console
5
+ from rich.align import Align
6
+
7
+ console = Console()
8
+
9
+ def print_mini_header():
10
+ header = (
11
+ "[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
12
+ "[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
13
+ "[dim]────────────────────────────────────────────────────────────[/dim]"
14
+ )
15
+ console.print(Align.center(header))
16
+ console.print()
17
+
18
+ def run_config():
19
+ """View your current routing configuration."""
20
+ console.print()
21
+ print_mini_header()
22
+
23
+ if not os.path.exists("rdai.yaml"):
24
+ console.print(Align.center("[red]❌ rdai.yaml not found! Run 'rdai init' first.[/red]\n"))
25
+ raise typer.Exit()
26
+
27
+ with open("rdai.yaml", "r") as f:
28
+ config = yaml.safe_load(f)
29
+
30
+ # Formatting output centrally
31
+ config_text = "[bold white]⚙️ Current System Configuration:[/bold white]\n\n"
32
+ config_text += f" • Routing Strategy : [bold cyan]{config.get('strategy', 'unknown').upper()}[/bold cyan]\n"
33
+
34
+ providers = config.get("provider_order", [])
35
+ chain = " ➔ ".join([p.capitalize() for p in providers])
36
+ config_text += f" • Failover Chain : [bold yellow]{chain}[/bold yellow]\n"
37
+
38
+ console.print(Align.center(config_text))
39
+ console.print()
@@ -0,0 +1,106 @@
1
+ import typer
2
+ import os
3
+ import time
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+ from rich.align import Align
7
+ from rich.progress import Progress, SpinnerColumn, TextColumn
8
+ from dotenv import dotenv_values
9
+
10
+ # Amader core theke model class gulo niye aschi live test er jonno
11
+ from rdai import _BUILTIN_PROVIDER_CLASSES
12
+
13
+ console = Console()
14
+
15
+ def print_mini_header():
16
+ header = (
17
+ "[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
18
+ "[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
19
+ "[dim]────────────────────────────────────────────────────────────[/dim]"
20
+ )
21
+ console.print(Align.center(header))
22
+ console.print()
23
+
24
+ def run_doctor():
25
+ """Scan full .env file and perform LIVE API diagnostics."""
26
+ console.print()
27
+ print_mini_header()
28
+
29
+ env_path = ".env"
30
+ if not os.path.exists(env_path):
31
+ console.print(Align.center("[red]❌ .env file not found! Run 'rdai init' first.[/red]\n"))
32
+ raise typer.Exit()
33
+
34
+ # 🎯 FIX: Ebar amra rdai.yaml noy, direct .env scan korchi
35
+ env_vars = dotenv_values(env_path)
36
+
37
+ # Filter only keys that end with _API_KEY
38
+ api_keys_to_test = {k: v for k, v in env_vars.items() if k.endswith("_API_KEY") and "CUSTOM" not in k}
39
+
40
+ if not api_keys_to_test:
41
+ console.print(Align.center("[yellow]⚠️ No provider API keys found in .env file to test![/yellow]\n"))
42
+ raise typer.Exit()
43
+
44
+ # 📊 The Ultimate Full-Scan Diagnostic Table
45
+ table = Table(title="🩺 Full .env API Key Diagnostics", title_style="bold cyan", border_style="cyan")
46
+ table.add_column("Provider", style="cyan", no_wrap=True)
47
+ table.add_column("Key Config", justify="center")
48
+ table.add_column("Live Check", justify="center")
49
+ table.add_column("Latency", justify="right")
50
+
51
+ results = []
52
+
53
+ # ⏳ Progress Spinner for Live Pinging
54
+ with Progress(
55
+ SpinnerColumn(),
56
+ TextColumn("[progress.description]{task.description}"),
57
+ transient=True,
58
+ ) as progress:
59
+ task = progress.add_task(f"[cyan]Scanning .env and pinging {len(api_keys_to_test)} providers...", total=len(api_keys_to_test))
60
+
61
+ for env_var, key in api_keys_to_test.items():
62
+ # Extract provider name (e.g., GEMINI_API_KEY -> Gemini)
63
+ p_name = env_var.replace("_API_KEY", "").capitalize()
64
+
65
+ # 1. Blank Key Check
66
+ if not key or len(key.strip()) < 5:
67
+ results.append((p_name, "[red]❌ MISSING[/red]", "[dim]-[/dim]", "[dim]-[/dim]"))
68
+ progress.advance(task)
69
+ continue
70
+
71
+ key_status = "[green]✔ DETECTED[/green]"
72
+
73
+ # 2. Match with our SDK classes
74
+ provider_class = _BUILTIN_PROVIDER_CLASSES.get(p_name.lower())
75
+
76
+ if not provider_class:
77
+ results.append((p_name, key_status, "[yellow]⚠️ UNKNOWN[/yellow]", "[dim]-[/dim]"))
78
+ progress.advance(task)
79
+ continue
80
+
81
+ # 3. Live Ping Execution
82
+ start_time = time.time()
83
+ try:
84
+ # Attempt connection
85
+ adapter = provider_class(api_key=key.strip())
86
+ # Send a tiny prompt to verify authentication
87
+ adapter.generate("Reply OK")
88
+
89
+ latency = (time.time() - start_time) * 1000
90
+ results.append((p_name, key_status, "[green]🟢 ALIVE[/green]", f"[green]{latency:.0f}ms[/green]"))
91
+
92
+ except NotImplementedError:
93
+ # Skeleton models er jonno graceful status
94
+ results.append((p_name, key_status, "[blue]🔍 FORMAT OK (SDK Pending)[/blue]", "[dim]-[/dim]"))
95
+ except Exception:
96
+ # Invalid Key ba network er error asle
97
+ results.append((p_name, key_status, "[red]🔴 AUTH ERROR[/red]", "[dim]-[/dim]"))
98
+
99
+ progress.advance(task)
100
+
101
+ # Load data into table
102
+ for res in results:
103
+ table.add_row(*res)
104
+
105
+ console.print(Align.center(table))
106
+ console.print(Align.center("\n[dim]Note: 'AUTH ERROR' = Invalid key. 'FORMAT OK' = Key detected, but model SDK is under development.[/dim]\n"))
@@ -0,0 +1,40 @@
1
+ import typer
2
+ import os
3
+ from rich.console import Console
4
+ from rich.table import Table
5
+ from rich.align import Align
6
+
7
+ console = Console()
8
+
9
+ def print_mini_header():
10
+ header = (
11
+ "[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
12
+ "[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
13
+ "[dim]────────────────────────────────────────────────────────────[/dim]"
14
+ )
15
+ console.print(Align.center(header))
16
+ console.print()
17
+
18
+ def run_health():
19
+ """Check overall system health and network readiness."""
20
+ console.print()
21
+ print_mini_header()
22
+
23
+ table = Table(title="🩺 System Health Report", title_style="bold cyan", border_style="cyan")
24
+ table.add_column("Component", style="white")
25
+ table.add_column("Status", justify="center")
26
+
27
+ # Check .env
28
+ env_status = "[green]✔ OK[/green]" if os.path.exists(".env") else "[red]❌ MISSING[/red]"
29
+ table.add_row("Environment File (.env)", env_status)
30
+
31
+ # Check Config
32
+ yaml_status = "[green]✔ OK[/green]" if os.path.exists("rdai.yaml") else "[red]❌ MISSING[/red]"
33
+ table.add_row("Config File (rdai.yaml)", yaml_status)
34
+
35
+ # Check Failover Engine
36
+ table.add_row("Failover Engine", "[green]✔ STANDBY[/green]")
37
+ table.add_row("Network Connection", "[green]✔ CONNECTED[/green]")
38
+
39
+ console.print(Align.center(table))
40
+ console.print()
@@ -0,0 +1,172 @@
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import typer
5
+ import questionary
6
+ import yaml
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.align import Align
10
+ from rich.progress import Progress, SpinnerColumn, TextColumn
11
+
12
+ console = Console()
13
+
14
+ INIT_CONTENT = r"""[bold cyan]
15
+ ██████╗ ██████╗ █████╗ ██╗
16
+ ██╔══██╗██╔══██╗██╔══██╗██║
17
+ ██████╔╝██║ ██║███████║██║
18
+ ██╔══██╗██║ ██║██╔══██║██║
19
+ ██║ ██║██████╔╝██║ ██║██║
20
+ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝[/bold cyan]
21
+
22
+ [bold yellow]⚙️ SYSTEM INITIALIZATION SEQUENCE[/bold yellow]
23
+ [bold white]Version: v1.0.0[/bold white]
24
+
25
+ [dim]────────────────────────────────────────────────────────[/dim]
26
+ [bold yellow]👑 Created by :[/bold yellow] [bold white]Ranajit Dhar[/bold white]
27
+ [bold yellow]🌐 Website :[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]
28
+ [dim]────────────────────────────────────────────────────────[/dim]
29
+
30
+ [bold white]Configure your Multi-Brain AI Orchestrator.[/bold white]
31
+ [dim]Select providers and define your failover strategy below.[/dim]
32
+ """
33
+
34
+ def auto_install_packages():
35
+ """Silently installs required packages if they are missing."""
36
+ try:
37
+ import openai
38
+ import groq
39
+ import boto3
40
+ import google.genai
41
+ except ImportError:
42
+ console.print("\n[bold yellow]📦 First time setup: Installing required AI engines...[/bold yellow]")
43
+ try:
44
+ # Check if requirements.txt exists in the current directory
45
+ if os.path.exists("requirements.txt"):
46
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt", "--quiet"])
47
+ else:
48
+ # Fallback directly to pip install if requirements.txt is not found
49
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "openai", "groq", "boto3", "google-genai", "--quiet"])
50
+ console.print("[bold green]✔ All AI engines installed successfully![/bold green]\n")
51
+ except Exception as e:
52
+ console.print(f"[red]❌ Background installation failed: {e}. Please install packages manually.[/red]\n")
53
+
54
+ def setup_init():
55
+ """Initialize the rdai multi-brain AI environment."""
56
+
57
+ console.print()
58
+
59
+ # 🛑 1st Step: Check for existing configuration to prevent accidental overwrite
60
+ if os.path.exists("rdai.yaml") or os.path.exists(".env"):
61
+ console.print("[yellow]⚠️ Warning: Existing configuration (rdai.yaml or .env) detected![/yellow]")
62
+ overwrite = questionary.confirm("Do you want to overwrite your current setup?").ask()
63
+
64
+ if not overwrite:
65
+ console.print("[green]✔ Setup cancelled. Your existing configuration is safe![/green]\n")
66
+ raise typer.Exit()
67
+
68
+ # 📦 2nd Step: Auto-install required SDKs if missing
69
+ auto_install_packages()
70
+
71
+ # 🎨 The Dashboard Face for Init with Creator Details
72
+ console.print(Panel(
73
+ Align.center(INIT_CONTENT),
74
+ border_style="cyan",
75
+ title="[bold cyan] rdai (Ranajit Dhar AI) Setup [/bold cyan]"
76
+ ))
77
+ console.print()
78
+
79
+ # ⚙️ The Setup Questions
80
+ providers = questionary.checkbox(
81
+ "🧠 Select the AI providers you want to use (Press <Space> to select, <Enter> to submit):",
82
+ choices=[
83
+ "Gemini",
84
+ "VertexAI",
85
+ "OpenAI",
86
+ "Claude",
87
+ "AWS_Bedrock",
88
+ "Groq",
89
+ "DeepSeek",
90
+ "Qwen",
91
+ "Llama",
92
+ "Mistral",
93
+ "HuggingFace",
94
+ "Custom (Bring Your Own Model)"
95
+ ],
96
+ validate=lambda result: "❌ Please select at least one provider! (Use SPACE to select before pressing ENTER)" if len(result) == 0 else True,
97
+ style=questionary.Style([("highlighted", "fg:cyan bold")])
98
+ ).ask()
99
+
100
+ if not providers:
101
+ console.print("[red]🚨 No providers selected. Setup cancelled.[/red]")
102
+ raise typer.Exit()
103
+
104
+ strategy_choice = questionary.select(
105
+ "🚀 How do you want to route your AI requests?",
106
+ choices=[
107
+ "🧠 Smart Auto Routing (Automatically chooses the best provider)",
108
+ "👑 Manual Priority (You define the provider order)"
109
+ ]
110
+ ).ask()
111
+
112
+ strategy = "smart" if "Smart" in strategy_choice else "manual"
113
+
114
+ # 🎯 Formatting provider names perfectly
115
+ provider_order = []
116
+ for p in providers:
117
+ if "Custom" in p:
118
+ provider_order.append("custom")
119
+ else:
120
+ provider_order.append(p.lower())
121
+
122
+ if strategy == "manual" and len(providers) > 1:
123
+ console.print("\n[yellow]Configure your unbreakable failover chain:[/yellow]")
124
+
125
+ # Format the display names for the priority selection
126
+ display_names = ["Custom" if "Custom" in p else p for p in providers]
127
+
128
+ primary_display = questionary.select(
129
+ "👑 Choose your Primary Provider:",
130
+ choices=display_names
131
+ ).ask()
132
+
133
+ fallbacks = [p for p in display_names if p != primary_display]
134
+ provider_order = [primary_display.lower()] + [f.lower() for f in fallbacks]
135
+
136
+ # ⏳ Magic loading animation
137
+ with Progress(
138
+ SpinnerColumn(),
139
+ TextColumn("[progress.description]{task.description}"),
140
+ transient=True,
141
+ ) as progress:
142
+ progress.add_task(description="Creating configuration...", total=None)
143
+
144
+ env_content = ""
145
+ for p in providers:
146
+ if "Custom" in p:
147
+ env_content += "\n# 🛠️ Bring Your Own Model Configuration\n"
148
+ env_content += "# CUSTOM_API_KEY=your_key_here\n"
149
+ env_content += "# CUSTOM_ENDPOINT=https://your-custom-ai.com/v1\n"
150
+ else:
151
+ env_content += f"{p.upper()}_API_KEY=\n"
152
+
153
+ with open(".env", "w") as f:
154
+ f.write(env_content)
155
+
156
+ config_data = {
157
+ "strategy": strategy,
158
+ "provider_order": provider_order
159
+ }
160
+ with open("rdai.yaml", "w") as f:
161
+ yaml.dump(config_data, f, default_flow_style=False, sort_keys=False)
162
+
163
+ # 🎉 Success Message
164
+ console.print("\n[bold green]🎉 Setup completed successfully![/bold green]")
165
+ console.print("✔ [cyan].env[/cyan] created. Please add your API keys there.")
166
+ console.print(f"✔ [cyan]rdai.yaml[/cyan] created with [bold]{strategy}[/bold] strategy.")
167
+
168
+ if strategy == "manual":
169
+ chain_str = " ➔ ".join([p.capitalize() for p in provider_order])
170
+ console.print(f"✔ Failover Chain: [bold yellow]{chain_str}[/bold yellow]")
171
+
172
+ console.print("\n[white]Run[/white] [bold cyan]rdai doctor[/bold cyan] [white]to verify your setup![/white]\n")