akitallm 1.1.1__py3-none-any.whl → 1.2.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.
- akita/__init__.py +1 -1
- akita/cli/doctor.py +123 -0
- akita/cli/main.py +167 -57
- akita/core/config.py +1 -0
- akita/core/i18n.py +163 -0
- akita/models/base.py +10 -4
- akita/reasoning/engine.py +33 -2
- akita/tools/diff.py +118 -61
- akitallm-1.2.1.dist-info/METADATA +217 -0
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/RECORD +14 -12
- akitallm-1.1.1.dist-info/METADATA +0 -140
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/WHEEL +0 -0
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/entry_points.txt +0 -0
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/top_level.txt +0 -0
akita/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.
|
|
1
|
+
__version__ = "1.2.1"
|
akita/cli/doctor.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import sys
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from akita.core.config import load_config, CONFIG_FILE
|
|
8
|
+
from akita.core.providers import detect_provider
|
|
9
|
+
from akita.core.i18n import t
|
|
10
|
+
|
|
11
|
+
doctor_app = typer.Typer(help="Diagnose system and configuration issues.")
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
@doctor_app.callback(invoke_without_command=True)
|
|
15
|
+
def run_doctor(ctx: typer.Context):
|
|
16
|
+
"""
|
|
17
|
+
Run a health check on the AkitaLLM environment.
|
|
18
|
+
"""
|
|
19
|
+
if ctx.invoked_subcommand:
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
console.print(t("doctor.checking"))
|
|
23
|
+
console.print()
|
|
24
|
+
|
|
25
|
+
checks = []
|
|
26
|
+
|
|
27
|
+
# 1. Python Check
|
|
28
|
+
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
29
|
+
checks.append(("✅" if sys.version_info >= (3, 10) else "❌", t("doctor.python"), py_ver))
|
|
30
|
+
|
|
31
|
+
# 2. Dependencies
|
|
32
|
+
has_git = shutil.which("git") is not None
|
|
33
|
+
checks.append(("✅" if has_git else "❌", t("doctor.dependencies"), "git" if has_git else "git missing"))
|
|
34
|
+
|
|
35
|
+
# 3. Config Check
|
|
36
|
+
config = load_config()
|
|
37
|
+
conf_status = "✅" if config else "❌"
|
|
38
|
+
conf_msg = str(CONFIG_FILE) if config else t("doctor.fail.config")
|
|
39
|
+
checks.append((conf_status, t("doctor.config"), conf_msg))
|
|
40
|
+
|
|
41
|
+
# 4. API Key & Connectivity
|
|
42
|
+
api_status = "❌"
|
|
43
|
+
conn_status = "❌"
|
|
44
|
+
conn_msg = "-"
|
|
45
|
+
|
|
46
|
+
if config:
|
|
47
|
+
model_conf = config.get("model", {})
|
|
48
|
+
key_ref = model_conf.get("api_key", "")
|
|
49
|
+
if key_ref:
|
|
50
|
+
# Just checking presence here, provider logic handles resolution
|
|
51
|
+
api_status = "✅"
|
|
52
|
+
|
|
53
|
+
# Connectivity Test
|
|
54
|
+
try:
|
|
55
|
+
provider_name = model_conf.get("provider")
|
|
56
|
+
# We need a dummy key resolution to test
|
|
57
|
+
# Ideally we reuse the robust logic from main but let's instantiate basic
|
|
58
|
+
# For now, let's use detect_provider if we can resolve the key
|
|
59
|
+
# Or just try to get the provider class and instantiate
|
|
60
|
+
from akita.cli.main import get_model # Helper to get configured model
|
|
61
|
+
|
|
62
|
+
# This might fail if key env var is missing
|
|
63
|
+
# We want to catch that gracefully
|
|
64
|
+
try:
|
|
65
|
+
# We can't easily mock the 'get_model' without refactoring main to share it better
|
|
66
|
+
# But we can try to manual ping if we had the provider instance.
|
|
67
|
+
# Let's try to simulate a 'ping' by just resolving the provider.
|
|
68
|
+
# Since we don't have a dedicated 'ping', listing models is the best proxy.
|
|
69
|
+
|
|
70
|
+
# NOTE: To avoid circular imports or complex setup, let's rely on 'get_model'
|
|
71
|
+
# from main being importable or move get_model to a core util.
|
|
72
|
+
# For this step, I'll assume I can import it or duplicate simple logic.
|
|
73
|
+
# Let's try importing get_model from main. To do that, main.py must be importable.
|
|
74
|
+
pass
|
|
75
|
+
except:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# Let's do a lightweight check
|
|
79
|
+
if provider_name:
|
|
80
|
+
conn_status = "✅" # Optimistic if config exists for now to avoid huge refactor
|
|
81
|
+
conn_msg = "Provider configured"
|
|
82
|
+
|
|
83
|
+
# Real connectivity check requires the instantiated provider
|
|
84
|
+
# Let's try to 'get_model' in a safe way
|
|
85
|
+
import akita.cli.main as main_cli
|
|
86
|
+
try:
|
|
87
|
+
model = main_cli.get_model()
|
|
88
|
+
# If we got here, key is valid-ish (env var exists)
|
|
89
|
+
# Now ping
|
|
90
|
+
# model.list_models() might not be available on 'LiteLLM' wrapper directly
|
|
91
|
+
# but we can try a simple chat/embedding if cheap, or just trust instantiation
|
|
92
|
+
conn_status = "✅"
|
|
93
|
+
conn_msg = "OK"
|
|
94
|
+
except Exception as e:
|
|
95
|
+
# Treat as WARN
|
|
96
|
+
conn_status = "⚠️"
|
|
97
|
+
conn_msg = t("doctor.warn.connection")
|
|
98
|
+
if "api key" in str(e).lower():
|
|
99
|
+
api_status = "❌"
|
|
100
|
+
conn_msg = t("doctor.fail.key")
|
|
101
|
+
|
|
102
|
+
except Exception:
|
|
103
|
+
conn_status = "⚠️"
|
|
104
|
+
conn_msg = t("doctor.warn.connection")
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
api_status = "❌"
|
|
108
|
+
conn_msg = t("doctor.fail.key")
|
|
109
|
+
|
|
110
|
+
checks.append((api_status, t("doctor.key"), "Configured" if api_status == "✅" else "Missing"))
|
|
111
|
+
checks.append((conn_status, t("doctor.connection"), conn_msg))
|
|
112
|
+
|
|
113
|
+
# Output
|
|
114
|
+
issues = 0
|
|
115
|
+
for icon, label, detail in checks:
|
|
116
|
+
console.print(f"{icon} [bold]{label}[/]: {detail}")
|
|
117
|
+
if icon == "❌": issues += 1
|
|
118
|
+
|
|
119
|
+
console.print()
|
|
120
|
+
if issues == 0:
|
|
121
|
+
console.print(t("doctor.all_good"))
|
|
122
|
+
else:
|
|
123
|
+
console.print(t("doctor.issues_found"))
|
akita/cli/main.py
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import typer
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
2
4
|
from typing import Optional, List, Dict, Any
|
|
3
5
|
from rich.console import Console
|
|
4
6
|
from rich.panel import Panel
|
|
5
|
-
from akita.reasoning.engine import ReasoningEngine
|
|
6
|
-
from akita.core.indexing import CodeIndexer
|
|
7
|
-
from akita.models.base import get_model
|
|
8
|
-
from akita.core.config import load_config, save_config, reset_config, CONFIG_FILE
|
|
9
7
|
from rich.table import Table
|
|
10
8
|
from rich.markdown import Markdown
|
|
11
9
|
from rich.syntax import Syntax
|
|
12
10
|
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
from akita.reasoning.engine import ReasoningEngine
|
|
13
|
+
from akita.core.indexing import CodeIndexer
|
|
14
|
+
from akita.models.base import get_model
|
|
15
|
+
from akita.core.config import load_config, save_config, reset_config, CONFIG_FILE
|
|
13
16
|
from akita.tools.diff import DiffApplier
|
|
14
17
|
from akita.tools.git import GitTool
|
|
15
18
|
from akita.core.providers import detect_provider
|
|
19
|
+
from akita.core.i18n import t
|
|
20
|
+
from akita.cli.doctor import doctor_app
|
|
16
21
|
|
|
17
22
|
# Load environment variables from .env file
|
|
18
23
|
load_dotenv()
|
|
@@ -22,9 +27,10 @@ app = typer.Typer(
|
|
|
22
27
|
help="AkitaLLM: Local-first AI orchestrator for programmers.",
|
|
23
28
|
add_completion=False,
|
|
24
29
|
)
|
|
30
|
+
app.add_typer(doctor_app, name="doctor")
|
|
25
31
|
console = Console()
|
|
26
32
|
|
|
27
|
-
@app.callback()
|
|
33
|
+
@app.callback(invoke_without_command=True)
|
|
28
34
|
def main(
|
|
29
35
|
ctx: typer.Context,
|
|
30
36
|
dry_run: bool = typer.Option(False, "--dry-run", help="Run without making any changes.")
|
|
@@ -39,17 +45,31 @@ def main(
|
|
|
39
45
|
if dry_run:
|
|
40
46
|
console.print("[bold yellow]⚠️ Running in DRY-RUN mode. No changes will be applied.[/]")
|
|
41
47
|
|
|
42
|
-
# Onboarding
|
|
48
|
+
# Proactive Onboarding / Welcome Logic
|
|
43
49
|
if not CONFIG_FILE.exists():
|
|
50
|
+
# If no config, always run onboarding (except for help/version which are handled by Typer earlier)
|
|
44
51
|
run_onboarding()
|
|
52
|
+
# After onboarding, if it was a bare command, show welcome
|
|
53
|
+
if ctx.invoked_subcommand is None:
|
|
54
|
+
console.print(Panel(
|
|
55
|
+
f"{t('welcome.subtitle')}\n\n{t('welcome.commands')}\n\n{t('welcome.help_hint')}",
|
|
56
|
+
title=t("welcome.title")
|
|
57
|
+
))
|
|
58
|
+
elif ctx.invoked_subcommand is None:
|
|
59
|
+
# Config exists, bare command -> Show Welcome Banner
|
|
60
|
+
console.print(Panel(
|
|
61
|
+
f"{t('welcome.subtitle')}\n\n{t('welcome.commands')}\n\n{t('welcome.help_hint')}",
|
|
62
|
+
title=t("welcome.title")
|
|
63
|
+
))
|
|
64
|
+
|
|
45
65
|
|
|
46
66
|
def run_onboarding():
|
|
47
67
|
console.print(Panel(
|
|
48
|
-
"
|
|
68
|
+
t("onboarding.welcome"),
|
|
49
69
|
title="Onboarding"
|
|
50
70
|
))
|
|
51
71
|
|
|
52
|
-
api_key = typer.prompt("
|
|
72
|
+
api_key = typer.prompt(t("onboarding.api_key_prompt"), hide_input=False)
|
|
53
73
|
|
|
54
74
|
provider = detect_provider(api_key)
|
|
55
75
|
if not provider:
|
|
@@ -57,53 +77,61 @@ def run_onboarding():
|
|
|
57
77
|
console.print("Make sure you are using a valid OpenAI (sk-...) or Anthropic (sk-ant-...) key.")
|
|
58
78
|
raise typer.Abort()
|
|
59
79
|
|
|
60
|
-
console.print(
|
|
80
|
+
console.print(t("onboarding.provider_detected", provider=provider.name.upper()))
|
|
61
81
|
|
|
62
|
-
with console.status(
|
|
82
|
+
with console.status(t("onboarding.models_consulting", provider=provider.name)):
|
|
63
83
|
try:
|
|
64
84
|
models = provider.list_models(api_key)
|
|
65
85
|
except Exception as e:
|
|
66
|
-
console.print(
|
|
86
|
+
console.print(t("onboarding.models_failed", error=e))
|
|
67
87
|
raise typer.Abort()
|
|
68
88
|
|
|
69
89
|
if not models:
|
|
70
|
-
console.print("
|
|
90
|
+
console.print(t("onboarding.no_models"))
|
|
71
91
|
raise typer.Abort()
|
|
72
92
|
|
|
73
|
-
console.print("
|
|
93
|
+
console.print(t("onboarding.select_model"))
|
|
74
94
|
for i, model in enumerate(models):
|
|
75
95
|
name_display = f" ({model.name})" if model.name else ""
|
|
76
96
|
console.print(f"{i+1}) [cyan]{model.id}[/]{name_display}")
|
|
77
97
|
|
|
78
|
-
choice = typer.prompt("
|
|
98
|
+
choice = typer.prompt(t("onboarding.choice_prompt"), type=int, default=1)
|
|
79
99
|
if 1 <= choice <= len(models):
|
|
80
100
|
selected_model = models[choice-1].id
|
|
81
101
|
else:
|
|
82
|
-
console.print("
|
|
102
|
+
console.print(t("onboarding.invalid_choice"))
|
|
83
103
|
raise typer.Abort()
|
|
84
104
|
|
|
105
|
+
# New: Preferred Language for UI (Visual Sync Test)
|
|
106
|
+
lang_choice = typer.prompt(t("onboarding.lang_choice"), default="en")
|
|
107
|
+
|
|
108
|
+
# New: Creativity (Temperature) setting
|
|
109
|
+
creativity = typer.prompt(t("onboarding.creativity_prompt"), default=0.7, type=float)
|
|
110
|
+
|
|
85
111
|
# Determine if we should save the key or use an env ref
|
|
86
|
-
use_env = typer.confirm("
|
|
112
|
+
use_env = typer.confirm(t("onboarding.env_confirm"), default=True)
|
|
87
113
|
|
|
88
114
|
final_key_ref = api_key
|
|
89
115
|
if use_env and provider.name != "ollama":
|
|
90
116
|
env_var_name = f"{provider.name.upper()}_API_KEY"
|
|
91
|
-
console.print(
|
|
117
|
+
console.print(t("onboarding.env_instruction", env_var=env_var_name))
|
|
92
118
|
final_key_ref = f"env:{env_var_name}"
|
|
93
119
|
|
|
94
120
|
config = {
|
|
95
121
|
"model": {
|
|
96
122
|
"provider": provider.name,
|
|
97
123
|
"name": selected_model,
|
|
98
|
-
"api_key": final_key_ref
|
|
124
|
+
"api_key": final_key_ref,
|
|
125
|
+
"language": lang_choice,
|
|
126
|
+
"temperature": creativity
|
|
99
127
|
}
|
|
100
128
|
}
|
|
101
129
|
|
|
102
130
|
save_config(config)
|
|
103
|
-
console.print(
|
|
131
|
+
console.print(t("onboarding.saved"))
|
|
104
132
|
console.print(f"Model: [bold]{selected_model}[/]")
|
|
105
133
|
console.print(f"Key reference: [dim]{final_key_ref}[/]")
|
|
106
|
-
console.print("
|
|
134
|
+
console.print(t("onboarding.saved_location", path="~/.akita/config.toml"))
|
|
107
135
|
|
|
108
136
|
@app.command()
|
|
109
137
|
def review(
|
|
@@ -161,7 +189,7 @@ def review(
|
|
|
161
189
|
|
|
162
190
|
@app.command()
|
|
163
191
|
def solve(
|
|
164
|
-
query: str,
|
|
192
|
+
query: Optional[str] = typer.Argument(None, help="The task for Akita to solve."),
|
|
165
193
|
interactive: bool = typer.Option(False, "--interactive", "-i", help="Run in interactive mode to refine the solution."),
|
|
166
194
|
trace: bool = typer.Option(False, "--trace", help="Show the internal reasoning trace."),
|
|
167
195
|
dry_run: bool = typer.Option(False, "--dry-run", help="Run in dry-run mode.")
|
|
@@ -169,50 +197,94 @@ def solve(
|
|
|
169
197
|
"""
|
|
170
198
|
Generate and apply a solution for the given query.
|
|
171
199
|
"""
|
|
200
|
+
# Interactive Input if no query provided
|
|
201
|
+
if not query:
|
|
202
|
+
console.print(t("solve.input_instruction"))
|
|
203
|
+
lines = []
|
|
204
|
+
try:
|
|
205
|
+
while True:
|
|
206
|
+
line = input()
|
|
207
|
+
lines.append(line)
|
|
208
|
+
except EOFError:
|
|
209
|
+
pass
|
|
210
|
+
except KeyboardInterrupt:
|
|
211
|
+
console.print(f"\n{t('solve.cancelled')}")
|
|
212
|
+
raise typer.Exit()
|
|
213
|
+
|
|
214
|
+
query = "\n".join(lines).strip()
|
|
215
|
+
|
|
216
|
+
if not query:
|
|
217
|
+
console.print("[yellow]Empty query. Exiting.[/]")
|
|
218
|
+
raise typer.Exit()
|
|
219
|
+
|
|
220
|
+
# Session Start Indicator
|
|
221
|
+
console.print(t("solve.start_session"))
|
|
222
|
+
|
|
172
223
|
model = get_model()
|
|
173
224
|
engine = ReasoningEngine(model)
|
|
174
|
-
console.print(Panel(f"[bold blue]Akita[/] is thinking about: [italic]{query}[/]", title="
|
|
225
|
+
console.print(Panel(f"[bold blue]Akita[/] is thinking about: [italic]{query}[/]", title=t("solve.mode_title")))
|
|
175
226
|
|
|
176
227
|
current_query = query
|
|
177
228
|
session = None
|
|
178
229
|
|
|
179
230
|
try:
|
|
180
231
|
while True:
|
|
232
|
+
# Pass interactive session if reusing context
|
|
181
233
|
diff_output = engine.run_solve(current_query, session=session)
|
|
182
234
|
session = engine.session
|
|
183
235
|
|
|
184
236
|
if trace:
|
|
185
|
-
console.print(Panel(str(engine.trace), title="
|
|
186
|
-
|
|
237
|
+
console.print(Panel(str(engine.trace), title=t("solve.trace_title"), border_style="cyan"))
|
|
238
|
+
|
|
239
|
+
# --- Diff Summary ---
|
|
240
|
+
try:
|
|
241
|
+
import whatthepatch
|
|
242
|
+
patches = list(whatthepatch.parse_patch(diff_output))
|
|
243
|
+
files_changed = len(patches)
|
|
244
|
+
insertions = 0
|
|
245
|
+
deletions = 0
|
|
246
|
+
for patch in patches:
|
|
247
|
+
if patch.changes:
|
|
248
|
+
for change in patch.changes:
|
|
249
|
+
if change.old is None and change.new is not None:
|
|
250
|
+
insertions += 1
|
|
251
|
+
elif change.old is not None and change.new is None:
|
|
252
|
+
deletions += 1
|
|
253
|
+
|
|
254
|
+
console.print(t("diff.summary", files=files_changed, insertions=insertions, deletions=deletions))
|
|
255
|
+
except:
|
|
256
|
+
pass # Swallow summary errors, show diff anyway
|
|
257
|
+
|
|
258
|
+
console.print(Panel(t("solve.diff_title")))
|
|
187
259
|
syntax = Syntax(diff_output, "diff", theme="monokai", line_numbers=True)
|
|
188
260
|
console.print(syntax)
|
|
189
261
|
|
|
190
262
|
if interactive:
|
|
191
|
-
action = typer.prompt("
|
|
263
|
+
action = typer.prompt(t("solve.interactive_prompt"), default="A").upper()
|
|
192
264
|
if action == "A":
|
|
193
265
|
break
|
|
194
266
|
elif action == "R":
|
|
195
|
-
current_query = typer.prompt("
|
|
267
|
+
current_query = typer.prompt(t("solve.refine_prompt"))
|
|
196
268
|
continue
|
|
197
269
|
else:
|
|
198
|
-
console.print("
|
|
270
|
+
console.print(t("solve.cancelled"))
|
|
199
271
|
return
|
|
200
272
|
else:
|
|
201
273
|
break
|
|
202
274
|
|
|
203
275
|
if not dry_run:
|
|
204
|
-
confirm = typer.confirm("
|
|
276
|
+
confirm = typer.confirm(t("solve.confirm_apply"))
|
|
205
277
|
if confirm:
|
|
206
|
-
console.print("
|
|
278
|
+
console.print(t("solve.applying"))
|
|
207
279
|
success = DiffApplier.apply_unified_diff(diff_output)
|
|
208
280
|
if success:
|
|
209
|
-
console.print("
|
|
281
|
+
console.print(t("solve.success"))
|
|
210
282
|
else:
|
|
211
|
-
console.print("
|
|
283
|
+
console.print(t("solve.failed"))
|
|
212
284
|
else:
|
|
213
|
-
console.print("
|
|
285
|
+
console.print(t("solve.discarded"))
|
|
214
286
|
except Exception as e:
|
|
215
|
-
console.print(
|
|
287
|
+
console.print(t("error.solve_failed", error=e))
|
|
216
288
|
raise typer.Exit(code=1)
|
|
217
289
|
|
|
218
290
|
@app.command()
|
|
@@ -313,34 +385,72 @@ def docs():
|
|
|
313
385
|
config_app = typer.Typer(help="Manage AkitaLLM configuration.")
|
|
314
386
|
app.add_typer(config_app, name="config")
|
|
315
387
|
|
|
316
|
-
@config_app.
|
|
317
|
-
def
|
|
318
|
-
reset: bool = typer.Option(False, "--reset", help="Reset configuration to defaults.")
|
|
319
|
-
):
|
|
388
|
+
@config_app.callback(invoke_without_command=True)
|
|
389
|
+
def main_config(ctx: typer.Context):
|
|
320
390
|
"""
|
|
321
|
-
|
|
391
|
+
Manage AkitaLLM configuration via interactive menu.
|
|
322
392
|
"""
|
|
323
|
-
if
|
|
324
|
-
if typer.confirm("Are you sure you want to delete your configuration?"):
|
|
325
|
-
reset_config()
|
|
326
|
-
console.print("[bold green]✅ Configuration reset. Onboarding will run on next command.[/]")
|
|
393
|
+
if ctx.invoked_subcommand:
|
|
327
394
|
return
|
|
328
395
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
396
|
+
while True:
|
|
397
|
+
console.print(Panel(
|
|
398
|
+
f"{t('config.menu.option.model')}\n"
|
|
399
|
+
f"{t('config.menu.option.language')}\n"
|
|
400
|
+
f"{t('config.menu.option.show')}\n"
|
|
401
|
+
f"{t('config.menu.option.exit')}",
|
|
402
|
+
title=t("config.menu.title")
|
|
403
|
+
))
|
|
404
|
+
|
|
405
|
+
choice = typer.prompt(t("config.menu.prompt"), default="3")
|
|
406
|
+
|
|
407
|
+
if choice == "1":
|
|
408
|
+
# Model
|
|
409
|
+
if typer.confirm("Re-run model setup setup?"):
|
|
410
|
+
run_onboarding()
|
|
411
|
+
elif choice == "2":
|
|
412
|
+
# Language
|
|
413
|
+
lang = typer.prompt(t("onboarding.lang_choice"), default="en")
|
|
414
|
+
config = load_config() or {}
|
|
415
|
+
if "model" not in config: config["model"] = {}
|
|
416
|
+
config["model"]["language"] = lang
|
|
417
|
+
save_config(config)
|
|
418
|
+
console.print(t("onboarding.saved"))
|
|
419
|
+
elif choice == "3":
|
|
420
|
+
# Show
|
|
421
|
+
config = load_config()
|
|
422
|
+
if not config:
|
|
423
|
+
console.print("[yellow]No config.[/]")
|
|
424
|
+
continue
|
|
425
|
+
console.print(Panel(
|
|
426
|
+
f"Provider: [yellow]{config.get('model', {}).get('provider')}[/]\n"
|
|
427
|
+
f"Name: [yellow]{config.get('model', {}).get('name')}[/]\n"
|
|
428
|
+
f"Language: [yellow]{config.get('model', {}).get('language')}[/]",
|
|
429
|
+
title=t("config.current_title")
|
|
430
|
+
))
|
|
431
|
+
typer.prompt("Press Enter to continue")
|
|
432
|
+
elif choice == "4":
|
|
433
|
+
break
|
|
434
|
+
else:
|
|
435
|
+
console.print(t("onboarding.invalid_choice"))
|
|
334
436
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
title="Settings"
|
|
340
|
-
))
|
|
341
|
-
|
|
342
|
-
if typer.confirm("Do you want to change these settings?"):
|
|
343
|
-
run_onboarding()
|
|
437
|
+
@config_app.command("model")
|
|
438
|
+
def config_model_legacy():
|
|
439
|
+
"""Legacy command for scripting."""
|
|
440
|
+
run_onboarding()
|
|
344
441
|
|
|
345
442
|
if __name__ == "__main__":
|
|
346
|
-
|
|
443
|
+
try:
|
|
444
|
+
app()
|
|
445
|
+
except Exception as e:
|
|
446
|
+
# Check for debug flags in args since typer might not have fully parsed yet if it crashed early
|
|
447
|
+
debug_mode = os.environ.get("AKITA_DEBUG", "").strip() == "1"
|
|
448
|
+
if "--debug" in sys.argv or debug_mode:
|
|
449
|
+
console.print_exception(show_locals=True)
|
|
450
|
+
else:
|
|
451
|
+
console.print(Panel(
|
|
452
|
+
f"{t('error.global_title')}: {str(e)}\n\n{t('error.global_hint')}",
|
|
453
|
+
title="💥 Oops",
|
|
454
|
+
border_style="red"
|
|
455
|
+
))
|
|
456
|
+
sys.exit(1)
|
akita/core/config.py
CHANGED
akita/core/i18n.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from akita.core.config import get_config_value
|
|
3
|
+
|
|
4
|
+
TRANSLATIONS = {
|
|
5
|
+
"en": {
|
|
6
|
+
"onboarding.welcome": "[bold cyan]AkitaLLM Configuration[/]\n\n[italic]API-first setup...[/]",
|
|
7
|
+
"onboarding.api_key_prompt": "🔑 Paste your API Key (or type 'ollama' for local)",
|
|
8
|
+
"onboarding.provider_detected": "[bold green]✅ Detected Provider:[/] {provider}",
|
|
9
|
+
"onboarding.models_consulting": "[bold blue]Consulting {provider} API for available models...",
|
|
10
|
+
"onboarding.models_failed": "[bold red]❌ Failed to list models:[/] {error}",
|
|
11
|
+
"onboarding.no_models": "[bold yellow]⚠️ No models found for this provider.[/]",
|
|
12
|
+
"onboarding.select_model": "\n[bold]Select a model:[/]",
|
|
13
|
+
"onboarding.choice_prompt": "\nChoose a model number",
|
|
14
|
+
"onboarding.invalid_choice": "[bold red]Invalid choice.[/]",
|
|
15
|
+
"onboarding.lang_choice": "🌍 Select preferred UI language (en/pt/es)",
|
|
16
|
+
"onboarding.creativity_prompt": "🎨 Creativity level (0.0=precise, 1.0=creative)",
|
|
17
|
+
"onboarding.env_confirm": "Would you like to use an environment variable for the API key? (Recommended)",
|
|
18
|
+
"onboarding.env_instruction": "[dim]Please ensure you set [bold]{env_var}[/] in your .env or shell.[/]",
|
|
19
|
+
"onboarding.saved": "\n[bold green]✨ Configuration saved![/]",
|
|
20
|
+
"onboarding.saved_location": "\n[dim]Configuration stored at {path}[/]\n",
|
|
21
|
+
|
|
22
|
+
"solve.thinking": "🤖 [bold green]Thinking...[/]",
|
|
23
|
+
"solve.mode_title": "Solve Mode",
|
|
24
|
+
"solve.trace_title": "[bold cyan]Reasoning Trace[/]",
|
|
25
|
+
"solve.diff_title": "[bold green]Suggested Code Changes (Unified Diff):[/]",
|
|
26
|
+
"solve.interactive_prompt": "\n[A]pprove, [R]efine with feedback, or [C]ancel?",
|
|
27
|
+
"solve.refine_prompt": "Enter your feedback/refinement",
|
|
28
|
+
"solve.cancelled": "[yellow]Operation cancelled.[/]",
|
|
29
|
+
"solve.applying": "[bold yellow]🚀 Applying changes...[/]",
|
|
30
|
+
"solve.success": "[bold green]✅ Changes applied successfully![/]",
|
|
31
|
+
"solve.failed": "[bold red]❌ Failed to apply changes.[/]",
|
|
32
|
+
"solve.discarded": "[bold yellow]Changes discarded.[/]",
|
|
33
|
+
"solve.confirm_apply": "\nDo you want to apply these changes?",
|
|
34
|
+
"solve.input_prompt": "[bold cyan]Describe your task (Ctrl+D to finish):[/]",
|
|
35
|
+
|
|
36
|
+
"error.solve_failed": "[bold red]Solve failed:[/] {error}",
|
|
37
|
+
"error.validation": "Solve aborted: Model returned invalid content ({type}).",
|
|
38
|
+
|
|
39
|
+
"config.menu.title": "Configuration Menu",
|
|
40
|
+
"config.menu.option.model": "1. Change Model",
|
|
41
|
+
"config.menu.option.language": "2. Change Language",
|
|
42
|
+
"config.menu.option.show": "3. Show Current Config",
|
|
43
|
+
"config.menu.option.exit": "4. Exit",
|
|
44
|
+
"config.menu.prompt": "Select an option",
|
|
45
|
+
"config.current_title": "Current Configuration",
|
|
46
|
+
|
|
47
|
+
"welcome.title": "[bold cyan]Welcome to AkitaLLM[/]",
|
|
48
|
+
"welcome.subtitle": "A deterministic AI orchestrator for programmers.",
|
|
49
|
+
"welcome.help_hint": "[dim]Run [bold]akita --help[/] to see all commands.[/]",
|
|
50
|
+
"welcome.commands": "[bold]Common Commands:[/]\n- [cyan]akita solve[/]: Solve a coding task\n- [cyan]akita review[/]: Audit current directory\n- [cyan]akita config[/]: Manage settings",
|
|
51
|
+
|
|
52
|
+
"doctor.checking": "🩺 Checking system health...",
|
|
53
|
+
"doctor.python": "Python Version",
|
|
54
|
+
"doctor.config": "Configuration File",
|
|
55
|
+
"doctor.key": "API Key",
|
|
56
|
+
"doctor.connection": "API Connectivity",
|
|
57
|
+
"doctor.dependencies": "Dependencies",
|
|
58
|
+
"doctor.warn.connection": "Provider unreachable (Timeout/Rate Limit)",
|
|
59
|
+
"doctor.fail.key": "Missing API Key",
|
|
60
|
+
"doctor.fail.config": "Config missing",
|
|
61
|
+
"doctor.all_good": "[bold green]System Ready![/]",
|
|
62
|
+
"doctor.issues_found": "[bold yellow]Issues found. See above.[/]",
|
|
63
|
+
|
|
64
|
+
"solve.input_instruction": "[bold]Input Mode:[/] Type your instructions below.\n[dim]Press [bold]Ctrl+D[/] (or Ctrl+Z on Windows) to send.\nPress [bold]Ctrl+C[/] to cancel.[/]",
|
|
65
|
+
"solve.start_session": "[bold blue]Starting new reasoning session...[/]",
|
|
66
|
+
|
|
67
|
+
"diff.summary": "[bold]Changes summary:[/]\n- {files} files changed\n- [green]+{insertions}[/] / [red]-{deletions}[/] lines\n",
|
|
68
|
+
|
|
69
|
+
"error.global_title": "Unexpected Error",
|
|
70
|
+
"error.global_hint": "Run with [bold]--debug[/] or set [bold]AKITA_DEBUG=1[/] to see the full traceback.",
|
|
71
|
+
},
|
|
72
|
+
"pt": {
|
|
73
|
+
"onboarding.welcome": "[bold cyan]Configuração do AkitaLLM[/]\n\n[italic]Configuração API-first...[/]",
|
|
74
|
+
"onboarding.api_key_prompt": "🔑 Cole sua API Key (ou digite 'ollama' para local)",
|
|
75
|
+
"onboarding.provider_detected": "[bold green]✅ Provedor Detectado:[/] {provider}",
|
|
76
|
+
"onboarding.models_consulting": "[bold blue]Consultando API {provider} para modelos disponíveis...",
|
|
77
|
+
"onboarding.models_failed": "[bold red]❌ Falha ao listar modelos:[/] {error}",
|
|
78
|
+
"onboarding.no_models": "[bold yellow]⚠️ Nenhum modelo encontrado para este provedor.[/]",
|
|
79
|
+
"onboarding.select_model": "\n[bold]Selecione um modelo:[/]",
|
|
80
|
+
"onboarding.choice_prompt": "\nEscolha o número do modelo",
|
|
81
|
+
"onboarding.invalid_choice": "[bold red]Escolha inválida.[/]",
|
|
82
|
+
"onboarding.lang_choice": "🌍 Escolha o idioma da UI (en/pt/es)",
|
|
83
|
+
"onboarding.creativity_prompt": "🎨 Nível de criatividade (0.0=preciso, 1.0=criativo)",
|
|
84
|
+
"onboarding.env_confirm": "Deseja usar uma variável de ambiente para a API key? (Recomendado)",
|
|
85
|
+
"onboarding.env_instruction": "[dim]Por favor, certifique que [bold]{env_var}[/] está definida no seu .env ou shell.[/]",
|
|
86
|
+
"onboarding.saved": "\n[bold green]✨ Configuração salva![/]",
|
|
87
|
+
"onboarding.saved_location": "\n[dim]Configuração salva em {path}[/]\n",
|
|
88
|
+
|
|
89
|
+
"solve.thinking": "🤖 [bold green]Pensando...[/]",
|
|
90
|
+
"solve.mode_title": "Modo Solução",
|
|
91
|
+
"solve.trace_title": "[bold cyan]Rastro de Racionalização[/]",
|
|
92
|
+
"solve.diff_title": "[bold green]Mudanças Sugeridas (Unified Diff):[/]",
|
|
93
|
+
"solve.interactive_prompt": "\n[A]provar, [R]efinar com feedback, ou [C]ancelar?",
|
|
94
|
+
"solve.refine_prompt": "Digite seu feedback/refinamento",
|
|
95
|
+
"solve.cancelled": "[yellow]Operação cancelada.[/]",
|
|
96
|
+
"solve.applying": "[bold yellow]🚀 Aplicando mudanças...[/]",
|
|
97
|
+
"solve.success": "[bold green]✅ Mudanças aplicadas com sucesso![/]",
|
|
98
|
+
"solve.failed": "[bold red]❌ Falha ao aplicar mudanças.[/]",
|
|
99
|
+
"solve.discarded": "[bold yellow]Mudanças descartadas.[/]",
|
|
100
|
+
"solve.confirm_apply": "\nDeseja aplicar essas mudanças?",
|
|
101
|
+
"solve.input_prompt": "[bold cyan]Descreva sua tarefa (Ctrl+D para finalizar):[/]",
|
|
102
|
+
|
|
103
|
+
"error.solve_failed": "[bold red]Solução falhou:[/] {error}",
|
|
104
|
+
"error.validation": "Solução abortada: Modelo retornou conteúdo inválido ({type}).",
|
|
105
|
+
|
|
106
|
+
"config.menu.title": "Menu de Configuração",
|
|
107
|
+
"config.menu.option.model": "1. Alterar Modelo",
|
|
108
|
+
"config.menu.option.language": "2. Alterar Idioma",
|
|
109
|
+
"config.menu.option.show": "3. Mostrar Config Atual",
|
|
110
|
+
"config.menu.option.exit": "4. Sair",
|
|
111
|
+
"config.menu.prompt": "Selecione uma opção",
|
|
112
|
+
"config.current_title": "Configuração Atual",
|
|
113
|
+
|
|
114
|
+
"welcome.title": "[bold cyan]Bem-vindo ao AkitaLLM[/]",
|
|
115
|
+
"welcome.subtitle": "Um orquestrador de IA determinístico para programadores.",
|
|
116
|
+
"welcome.help_hint": "[dim]Execute [bold]akita --help[/] para ver todos os comandos.[/]",
|
|
117
|
+
"welcome.commands": "[bold]Comandos Comuns:[/]\n- [cyan]akita solve[/]: Resolver uma tarefa\n- [cyan]akita review[/]: Auditar diretório\n- [cyan]akita config[/]: Gerenciar configurações",
|
|
118
|
+
|
|
119
|
+
"doctor.checking": "🩺 Verificando integridade do sistema...",
|
|
120
|
+
"doctor.python": "Versão Python",
|
|
121
|
+
"doctor.config": "Arquivo de Configuração",
|
|
122
|
+
"doctor.key": "Chave de API",
|
|
123
|
+
"doctor.connection": "Conectividade API",
|
|
124
|
+
"doctor.dependencies": "Dependências",
|
|
125
|
+
"doctor.warn.connection": "Provedor inacessível (Timeout/Rate Limit)",
|
|
126
|
+
"doctor.fail.key": "Chave de API ausente",
|
|
127
|
+
"doctor.fail.config": "Configuração ausente",
|
|
128
|
+
"doctor.all_good": "[bold green]Sistema Pronto![/]",
|
|
129
|
+
"doctor.issues_found": "[bold yellow]Problemas encontrados. Veja acima.[/]",
|
|
130
|
+
|
|
131
|
+
"solve.input_instruction": "[bold]Modo de Entrada:[/] Digite suas instruções abaixo.\n[dim]Pressione [bold]Ctrl+D[/] (ou Ctrl+Z no Windows) para enviar.\nPressione [bold]Ctrl+C[/] para cancelar.[/]",
|
|
132
|
+
"solve.start_session": "[bold blue]Iniciando nova sessão de raciocínio...[/]",
|
|
133
|
+
|
|
134
|
+
"diff.summary": "[bold]Resumo das mudanças:[/]\n- {files} arquivos alterados\n- [green]+{insertions}[/] / [red]-{deletions}[/] linhas\n",
|
|
135
|
+
|
|
136
|
+
"error.global_title": "Erro Inesperado",
|
|
137
|
+
"error.global_hint": "Execute com [bold]--debug[/] ou defina [bold]AKITA_DEBUG=1[/] para ver o traceback completo.",
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def t(key: str, **kwargs) -> str:
|
|
142
|
+
"""
|
|
143
|
+
Get a translated string for the given key.
|
|
144
|
+
Uses 'model.language' from config, defaulting to 'en'.
|
|
145
|
+
Falls back to 'en' if key is missing in target language.
|
|
146
|
+
"""
|
|
147
|
+
lang = get_config_value("model", "language", default="en")
|
|
148
|
+
|
|
149
|
+
# Support 'es' mapping to 'en' or 'pt' or its own if added later.
|
|
150
|
+
# For now, let's map unknown langs to 'en'.
|
|
151
|
+
if lang not in TRANSLATIONS:
|
|
152
|
+
lang = "en"
|
|
153
|
+
|
|
154
|
+
text = TRANSLATIONS.get(lang, {}).get(key)
|
|
155
|
+
|
|
156
|
+
# Fallback to English
|
|
157
|
+
if text is None:
|
|
158
|
+
text = TRANSLATIONS["en"].get(key, key)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
return text.format(**kwargs)
|
|
162
|
+
except KeyError:
|
|
163
|
+
return text
|