Skip to content

Commit b63151f

Browse files
authored
Merge pull request #571 from lbedner/ollama-bug
Fix Ollama Bug
2 parents 5aa6493 + d125318 commit b63151f

12 files changed

Lines changed: 280 additions & 37 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Each generated project includes:
3232

3333
## Installation
3434

35-
**Current Version**: 0.6.6
35+
**Current Version**: 0.6.7rc1
3636

3737
```bash
3838
pip install aegis-stack

aegis/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
Aegis Stack CLI - Component generation and project management tools.
33
"""
44

5-
__version__ = "0.6.6"
5+
__version__ = "0.6.7rc1"

aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,7 @@ def chat(
463463
if convos:
464464
conversation_id = convos[0].id
465465
typer.echo(
466-
f"Resuming conversation {conversation_id[:8]}... "
467-
"(use --new for fresh start)",
466+
t("ai.resuming_conversation", id=conversation_id[:8]),
468467
err=True,
469468
)
470469

@@ -1362,16 +1361,16 @@ def usage(
13621361
def display_summary_panel(stats: UsageStatsResponse) -> None:
13631362
success_color = get_success_color(stats.success_rate)
13641363
summary_lines = [
1365-
f"[bold cyan]Total Tokens:[/bold cyan] {format_number(stats.total_tokens)}",
1366-
f"[bold cyan]Total Cost:[/bold cyan] {format_cost(stats.total_cost)}",
1367-
f"[bold cyan]Total Requests:[/bold cyan] {format_number(stats.total_requests)}",
1368-
f"[bold cyan]Success Rate:[/bold cyan] "
1364+
f"[bold cyan]{t('ai.usage_total_tokens')}[/bold cyan] {format_number(stats.total_tokens)}",
1365+
f"[bold cyan]{t('ai.usage_total_cost')}[/bold cyan] {format_cost(stats.total_cost)}",
1366+
f"[bold cyan]{t('ai.usage_total_requests')}[/bold cyan] {format_number(stats.total_requests)}",
1367+
f"[bold cyan]{t('ai.usage_success_rate')}[/bold cyan] "
13691368
f"[{success_color}]{format_percentage(stats.success_rate)}[/{success_color}]",
13701369
]
13711370
console.print(
13721371
Panel(
13731372
"\n".join(summary_lines),
1374-
title="[bold magenta]AI Usage Summary[/bold magenta]",
1373+
title=f"[bold magenta]{t('ai.usage_title')}[/bold magenta]",
13751374
border_style="magenta",
13761375
padding=(1, 2),
13771376
)

aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/comms.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,21 @@
2727
"TWILIO_PHONE_NUMBER": "comms.warn.twilio_phone",
2828
}
2929

30+
# ConfigurationError messages from send/call functions
31+
_CONFIG_ERROR_KEYS: dict[str, str] = {
32+
"Twilio credentials not set": "comms.warn.twilio_creds",
33+
"RESEND_API_KEY is not set. Sign up": "comms.warn.resend_api_key_send",
34+
}
35+
3036

3137
def _translate_warning(warning: str) -> str:
32-
"""Translate known service-layer warnings at display time."""
38+
"""Translate known service-layer warnings and errors at display time."""
3339
for env_var, key in _WARNING_KEYS.items():
3440
if env_var in warning:
3541
return t(key)
42+
for prefix, key in _CONFIG_ERROR_KEYS.items():
43+
if warning.startswith(prefix):
44+
return t(key)
3645
return warning
3746

3847

@@ -217,7 +226,10 @@ async def _email_send(
217226
)
218227

219228
except EmailConfigurationError as e:
220-
typer.secho(t("comms.configuration_error", error=e), fg=typer.colors.RED)
229+
typer.secho(
230+
t("comms.configuration_error", error=_translate_warning(str(e))),
231+
fg=typer.colors.RED,
232+
)
221233
raise typer.Exit(1)
222234
except EmailError as e:
223235
typer.secho(t("comms.email_send_failed", error=e), fg=typer.colors.RED)
@@ -251,7 +263,10 @@ async def _sms_send(to: str, body: str) -> None:
251263
)
252264

253265
except SMSConfigurationError as e:
254-
typer.secho(t("comms.configuration_error", error=e), fg=typer.colors.RED)
266+
typer.secho(
267+
t("comms.configuration_error", error=_translate_warning(str(e))),
268+
fg=typer.colors.RED,
269+
)
255270
raise typer.Exit(1)
256271
except SMSError as e:
257272
typer.secho(t("comms.sms_send_failed", error=e), fg=typer.colors.RED)
@@ -292,7 +307,10 @@ async def _call_make(to: str, twiml_url: str, timeout: int) -> None:
292307
)
293308

294309
except CallConfigurationError as e:
295-
typer.secho(t("comms.configuration_error", error=e), fg=typer.colors.RED)
310+
typer.secho(
311+
t("comms.configuration_error", error=_translate_warning(str(e))),
312+
fg=typer.colors.RED,
313+
)
296314
raise typer.Exit(1)
297315
except CallError as e:
298316
typer.secho(t("comms.call_failed", error=e), fg=typer.colors.RED)

aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Command-line interface for system health checking and monitoring via API endpoin
66

77
import asyncio
88
import json
9+
import re
910
import sys
1011
from typing import Any
1112

@@ -28,6 +29,116 @@ from app.services.system.ui import get_status_color_name, get_status_icon
2829
app = typer.Typer(name="health", help=lazy_t("health.help"))
2930
console = Console()
3031

32+
# Pattern-based translation for health API response messages.
33+
# Maps English substrings/prefixes to i18n keys for display-time translation.
34+
_HEALTH_MSG_EXACT: dict[str, str] = {
35+
"Aegis Stack application": "health.msg.aegis_ok",
36+
"Aegis Stack has issues": "health.msg.aegis_issues",
37+
"Some components have issues": "health.msg.components_issues",
38+
"Some services have issues": "health.msg.services_issues",
39+
"System container metrics": "health.msg.system_ok",
40+
"System container has issues": "health.msg.system_issues",
41+
"Database connection successful": "health.msg.db_ok",
42+
"Database module not available": "health.msg.db_module_missing",
43+
"PostgreSQL server not reachable": "health.msg.db_not_reachable",
44+
"PostgreSQL authentication failed": "health.msg.db_auth_failed",
45+
"PostgreSQL database does not exist": "health.msg.db_not_exist",
46+
"Database not initialized - file does not exist": "health.msg.db_not_init",
47+
"Database file not accessible": "health.msg.db_not_accessible",
48+
"Redis cache connection and operations successful": "health.msg.cache_ok",
49+
"Cache library not installed": "health.msg.cache_not_installed",
50+
"No active workers": "health.msg.no_active_workers",
51+
"idle": "health.msg.idle",
52+
"configured - no functions defined": "health.msg.no_functions",
53+
"Auth service configured and ready": "health.msg.auth_ok",
54+
"AI service is disabled": "health.msg.ai_disabled",
55+
"No communication providers configured": "health.msg.comms_none",
56+
"Communications service fully configured": "health.msg.comms_ok",
57+
"Ollama server not reachable": "health.msg.ollama_not_reachable",
58+
"Ollama running but no models installed": "health.msg.ollama_no_models",
59+
"worker offline - no health check data": "health.msg.worker_offline",
60+
}
61+
62+
_HEALTH_MSG_PATTERNS: list[tuple[re.Pattern[str], str, str]] = [
63+
# (compiled_regex, i18n_key, group_mapping)
64+
(re.compile(r"^(\d+) components available$"), "health.msg.components_ok", "count"),
65+
(re.compile(r"^(\d+) services available$"), "health.msg.services_ok", "count"),
66+
(re.compile(r"^Memory usage: ([\d.]+)%$"), "health.msg.memory_usage", "pct"),
67+
(re.compile(r"^Disk usage: ([\d.]+)%$"), "health.msg.disk_usage", "pct"),
68+
(re.compile(r"^CPU usage: ([\d.]+)%$"), "health.msg.cpu_usage", "pct"),
69+
(re.compile(r"^Scheduler running with (\d+) tasks?$"), "health.msg.scheduler_running", "count"),
70+
(re.compile(r"^Database connection failed"), "health.msg.db_failed", ""),
71+
(re.compile(r"^FastAPI backend active"), "health.msg.backend_active", ""),
72+
(re.compile(r"^(arq|TaskIQ|Dramatiq) worker infrastructure"), "health.msg.worker_infra", "backend"),
73+
(re.compile(r"^(\d+)/(\d+) workers? active$"), "health.msg.workers_active", "active,total"),
74+
(re.compile(r"^(\d+) functional queues configured"), "health.msg.queues_configured", "count"),
75+
(re.compile(r"^\((\d+) (?:active|with consumers|with heartbeats)\)$"), "health.msg.queues_active", "count"),
76+
(re.compile(r"^(\d+) processing$"), "health.msg.processing", "count"),
77+
(re.compile(r"^(\d+) queued$"), "health.msg.queued", "count"),
78+
(re.compile(r"^(\d+) completed$"), "health.msg.completed", "count"),
79+
(re.compile(r"^(\d+) failed \(([\d.]+)%\)$"), "health.msg.failed", "count,pct"),
80+
(re.compile(r"^AI service ready"), "health.msg.ai_ready", ""),
81+
(re.compile(r"^Comms service partially configured"), "health.msg.comms_partial", ""),
82+
]
83+
84+
85+
_QUEUE_DESC_MAP: dict[str, str] = {
86+
"Load testing and performance testing": "health.msg.queue.load_test",
87+
"Image and file processing": "health.msg.queue.media",
88+
"System maintenance and monitoring tasks": "health.msg.queue.system",
89+
}
90+
91+
92+
def _translate_single_part(part: str) -> str:
93+
"""Translate a single status part (used for comma-separated segments)."""
94+
key = _HEALTH_MSG_EXACT.get(part)
95+
if key:
96+
return t(key)
97+
for pattern, i18n_key, groups in _HEALTH_MSG_PATTERNS:
98+
match = pattern.match(part)
99+
if match:
100+
if not groups:
101+
return t(i18n_key)
102+
group_names = groups.split(",")
103+
kwargs = {name: match.group(i + 1) for i, name in enumerate(group_names)}
104+
return t(i18n_key, **kwargs)
105+
return part
106+
107+
108+
def _translate_health_msg(msg: str) -> str:
109+
"""Translate a health API message at display time."""
110+
# Try exact match first
111+
key = _HEALTH_MSG_EXACT.get(msg)
112+
if key:
113+
return t(key)
114+
115+
# Try pattern matching
116+
for pattern, i18n_key, groups in _HEALTH_MSG_PATTERNS:
117+
match = pattern.match(msg)
118+
if match:
119+
if not groups:
120+
return t(i18n_key)
121+
group_names = groups.split(",")
122+
kwargs = {name: match.group(i + 1) for i, name in enumerate(group_names)}
123+
return t(i18n_key, **kwargs)
124+
125+
# Try composite "description: status" messages (e.g., queue messages)
126+
if ": " in msg:
127+
desc, status = msg.split(": ", 1)
128+
desc_key = _QUEUE_DESC_MAP.get(desc)
129+
# Translate each comma-separated status part individually
130+
status_parts = [p.strip() for p in status.split(", ")]
131+
translated_parts = []
132+
for part in status_parts:
133+
translated = _translate_single_part(part)
134+
translated_parts.append(translated)
135+
status = ", ".join(translated_parts)
136+
if desc_key:
137+
return f"{t(desc_key)}:{status}"
138+
139+
# No translation found — return original
140+
return msg
141+
31142

32143
def _get_status_icon_and_color(status: ComponentStatusType) -> tuple[str, str]:
33144
"""Get the appropriate icon and color for a component status (shared mapping)."""
@@ -296,7 +407,7 @@ def _display_sub_components(
296407
sub_line = f"{tree_connector}[{sub_color}]{sub_icon} {sub_name}[/{sub_color}]"
297408
if detailed and sub_component.response_time_ms is not None:
298409
sub_line += f" ([dim]{sub_component.response_time_ms:.1f}ms[/dim])"
299-
sub_line += f" {sub_component.message}"
410+
sub_line += f" {_translate_health_msg(sub_component.message)}"
300411
console.print(sub_line)
301412

302413
# Recursively display sub-sub-components
@@ -463,7 +574,7 @@ def _display_health_status(
463574
component_line = f"[{status_color}]{status_icon} {name}[/{status_color}]"
464575
if detailed and component.response_time_ms is not None:
465576
component_line += f" ([dim]{component.response_time_ms:.1f}ms[/dim])"
466-
component_line += f" {component.message}"
577+
component_line += f" {_translate_health_msg(component.message)}"
467578
console.print(component_line)
468579

469580
# Display sub-components with tree structure (recursive)
@@ -489,7 +600,11 @@ def _display_health_status(
489600
if system_info:
490601
sys_info_content = []
491602
for key, value in system_info.items():
492-
sys_info_content.append(f"{key.replace('_', ' ').title()}: {value}")
603+
label_key = f"health.sysinfo.{key}"
604+
label = t(label_key)
605+
if label == label_key:
606+
label = key.replace("_", " ").title()
607+
sys_info_content.append(f"{label}:{value}")
493608

494609
console.print(
495610
Panel(

aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,6 @@ app = typer.Typer(
2525
console = Console()
2626

2727

28-
def _translate_job_name(job_id: str, stored_name: str | None) -> str:
29-
"""Translate known job names at display time, fall back to stored name."""
30-
key = f"tasks.job.{job_id}"
31-
translated = t(key)
32-
# If t() returns the key itself, no translation exists — use stored name
33-
if translated == key:
34-
return stored_name or job_id
35-
return translated
36-
37-
3828
@app.command("list", help=lazy_t("tasks.help_list"))
3929
def list_jobs() -> None:
4030
rprint(f"[bold blue]{t('tasks.listing_jobs')}[/bold blue]")
@@ -67,7 +57,7 @@ def list_jobs() -> None:
6757

6858
table.add_row(
6959
task.job_id,
70-
_translate_job_name(task.job_id, task.name),
60+
task.name or task.job_id,
7161
(
7262
f"[{status_color}]"
7363
f"{t('tasks.active') if task.is_active else t('tasks.paused')}"

aegis/templates/copier-aegis-project/{{ project_slug }}/app/i18n/locales/en.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,63 @@
5454
"health.component_tree": "Component Tree",
5555
"health.component_service_tree": "Component & Service Tree",
5656
"health.system_info": "System Information",
57+
"health.sysinfo.python_version": "Python Version",
58+
"health.sysinfo.platform": "Platform",
59+
"health.sysinfo.containerized": "Containerized",
5760
# Help text
5861
"health.help": "System health monitoring commands",
5962
"health.help_status": "Show system health status (always exits 0 for inspection). Use --detailed for component metadata and response times.",
6063
"health.help_probe": "Health probe for monitoring — exits 1 if unhealthy (like k8s probes).",
6164
"health.opt_detailed": "Show detailed component information",
6265
"health.opt_json": "Output as JSON",
66+
# Health service messages (translated at display time from API responses)
67+
"health.msg.aegis_ok": "Aegis Stack application",
68+
"health.msg.aegis_issues": "Aegis Stack has issues",
69+
"health.msg.components_ok": "{count} components available",
70+
"health.msg.components_issues": "Some components have issues",
71+
"health.msg.services_ok": "{count} services available",
72+
"health.msg.services_issues": "Some services have issues",
73+
"health.msg.system_ok": "System container metrics",
74+
"health.msg.system_issues": "System container has issues",
75+
"health.msg.db_ok": "Database connection successful",
76+
"health.msg.db_module_missing": "Database module not available",
77+
"health.msg.db_not_reachable": "PostgreSQL server not reachable",
78+
"health.msg.db_auth_failed": "PostgreSQL authentication failed",
79+
"health.msg.db_not_exist": "PostgreSQL database does not exist",
80+
"health.msg.db_failed": "Database connection failed",
81+
"health.msg.db_not_init": "Database not initialized - file does not exist",
82+
"health.msg.db_not_accessible": "Database file not accessible",
83+
"health.msg.cache_ok": "Redis cache connection and operations successful",
84+
"health.msg.cache_not_installed": "Cache library not installed",
85+
"health.msg.memory_usage": "Memory usage: {pct}%",
86+
"health.msg.disk_usage": "Disk usage: {pct}%",
87+
"health.msg.cpu_usage": "CPU usage: {pct}%",
88+
"health.msg.scheduler_running": "Scheduler running with {count} tasks",
89+
"health.msg.backend_active": "FastAPI backend active",
90+
"health.msg.worker_infra": "{backend} worker infrastructure",
91+
"health.msg.workers_active": "{active}/{total} workers active",
92+
"health.msg.queues_configured": "{count} functional queues configured",
93+
"health.msg.queues_active": "({count} active)",
94+
"health.msg.no_active_workers": "No active workers",
95+
"health.msg.idle": "idle",
96+
"health.msg.processing": "{count} processing",
97+
"health.msg.queued": "{count} queued",
98+
"health.msg.completed": "{count} completed",
99+
"health.msg.no_functions": "configured - no functions defined",
100+
"health.msg.auth_ok": "Auth service configured and ready",
101+
"health.msg.ai_ready": "AI service ready",
102+
"health.msg.ai_disabled": "AI service is disabled",
103+
"health.msg.comms_none": "No communication providers configured",
104+
"health.msg.comms_partial": "Comms service partially configured",
105+
"health.msg.comms_ok": "Communications service fully configured",
106+
"health.msg.ollama_not_reachable": "Ollama server not reachable",
107+
"health.msg.ollama_no_models": "Ollama running but no models installed",
108+
"health.msg.worker_offline": "worker offline - no health check data",
109+
"health.msg.failed": "{count} failed ({pct}%)",
110+
# Queue descriptions
111+
"health.msg.queue.load_test": "Load testing and performance testing",
112+
"health.msg.queue.media": "Image and file processing",
113+
"health.msg.queue.system": "System maintenance and monitoring tasks",
63114
# ── RAG ───────────────────────────────────────────────────────────
64115
# Model management
65116
"rag.openai_key_missing": "OpenAI API key not configured.",
@@ -339,6 +390,8 @@
339390
"comms.warn.twilio_auth_token": "TWILIO_AUTH_TOKEN is not set. Find it in your Twilio Console dashboard.",
340391
"comms.warn.twilio_messaging_sid": "TWILIO_MESSAGING_SERVICE_SID or TWILIO_PHONE_NUMBER must be set. Messaging Service SID is required for toll-free numbers.",
341392
"comms.warn.twilio_phone": "TWILIO_PHONE_NUMBER is not set. This should be a Twilio phone number capable of making calls.",
393+
"comms.warn.twilio_creds": "Twilio credentials not set. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN environment variables.",
394+
"comms.warn.resend_api_key_send": "RESEND_API_KEY is not set. Sign up at https://resend.com and set your API key.",
342395
# Help text
343396
"comms.help": "Communications service commands (email, SMS, voice)",
344397
"comms.help_status": "Show communications service configuration status.",
@@ -406,8 +459,6 @@
406459
"tasks.paused": "Paused",
407460
"tasks.total_jobs": "Total jobs:",
408461
"tasks.list_failed": "Failed to list jobs:",
409-
# Known job display names (translated at display time, stored as English in DB)
410-
"tasks.job.database_backup": "Daily Database Backup",
411462
# Help text
412463
"tasks.help": "Scheduled task management commands",
413464
"tasks.help_list": "List all scheduled jobs with their current status and details.",
@@ -505,6 +556,12 @@
505556
"ai.prov_not_installed": "Not installed",
506557
"ai.prov_error": "Error",
507558
"ai.providers_tip": "Tip: Run '{app} ai add-provider <name>' to install missing providers.",
559+
# Usage panel
560+
"ai.usage_title": "AI Usage Summary",
561+
"ai.usage_total_tokens": "Total Tokens:",
562+
"ai.usage_total_cost": "Total Cost:",
563+
"ai.usage_total_requests": "Total Requests:",
564+
"ai.usage_success_rate": "Success Rate:",
508565
"ai.svc_initialized": "Initialized",
509566
"ai.svc_not_initialized": "Not initialized (lazy-loaded)",
510567
"ai.enabled": "Enabled",
@@ -561,6 +618,7 @@
561618
"ai.title_label": "Title: {title}",
562619
"ai.provider_info": "Provider: {provider}",
563620
"ai.messages_info": "Messages: {count}",
621+
"ai.resuming_conversation": "Resuming conversation {id}... (use --new for fresh start)",
564622
# Chat session
565623
"ai.initializing": "Initializing...",
566624
"ai.ok": "OK",

0 commit comments

Comments
 (0)