mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-21 15:14:45 +02:00
feat: !health personality with emojis, skip empty regions, AIDA physical node identity
- Replace build_lora_compact with personality version using emojis - Add _region_lora_compact helper for region-specific display - Skip empty regions in 3 loops (build_tier1_summary, utilization, list_regions_compact) - Update AIDA identity: she IS a physical node (!27780c47 AIDA-N2) - AIDA now knows she has real GPS coordinates and radio connections Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a97d346449
commit
1662d80f02
2 changed files with 118 additions and 22 deletions
|
|
@ -297,6 +297,8 @@ class MeshReporter:
|
|||
lines.append("REGIONS:")
|
||||
|
||||
for region in health.regions:
|
||||
if not region.node_ids:
|
||||
continue
|
||||
rs = region.score
|
||||
context = self._region_context(region.name)
|
||||
context_str = f" - {context}" if context else ""
|
||||
|
|
@ -708,6 +710,8 @@ class MeshReporter:
|
|||
|
||||
# Utilization issues
|
||||
for region in health.regions:
|
||||
if not region.node_ids:
|
||||
continue
|
||||
if region.score.util_percent >= 25:
|
||||
issues.append(
|
||||
f"{region.name}: channel utilization at {region.score.util_percent:.0f}% (High)"
|
||||
|
|
@ -1491,33 +1495,120 @@ class MeshReporter:
|
|||
return recs
|
||||
|
||||
def build_lora_compact(self, scope: str, scope_value: str = None) -> str:
|
||||
"""Build LoRa-optimized compact summary for !health command."""
|
||||
"""Build LoRa-optimized summary with personality for !health command."""
|
||||
health = self.health_engine.mesh_health
|
||||
if not health:
|
||||
return "Mesh: No data"
|
||||
return "📡 No mesh data yet."
|
||||
|
||||
if scope == "region" and scope_value:
|
||||
region = self._find_region(scope_value)
|
||||
if not region:
|
||||
return f"Region '{scope_value}' not found"
|
||||
return f"Region '{scope_value}' not found."
|
||||
return self._region_lora_compact(region)
|
||||
|
||||
if scope == "node" and scope_value:
|
||||
return self.build_node_compact(scope_value)
|
||||
|
||||
s = health.score
|
||||
|
||||
if s.composite >= 90:
|
||||
header = f"📡 freq51 looking great — {s.composite:.0f}/100"
|
||||
elif s.composite >= 75:
|
||||
header = f"📡 freq51 running solid — {s.composite:.0f}/100"
|
||||
elif s.composite >= 60:
|
||||
header = f"📡 freq51 needs attention — {s.composite:.0f}/100"
|
||||
else:
|
||||
header = f"🚨 freq51 struggling — {s.composite:.0f}/100"
|
||||
|
||||
lines = [header, ""]
|
||||
|
||||
offline_infra = [n for n in health.nodes.values() if n.is_infrastructure and not n.is_online]
|
||||
if offline_infra:
|
||||
offline_names = ", ".join(n.short_name or str(n.node_num) for n in offline_infra[:3])
|
||||
more = f" +{len(offline_infra)-3}" if len(offline_infra) > 3 else ""
|
||||
lines.append(f"🏗️ {s.infra_online}/{s.infra_total} routers up")
|
||||
lines.append(f" ❌ Down: {offline_names}{more}")
|
||||
else:
|
||||
lines.append(f"🏗️ All {s.infra_total} routers up ✅")
|
||||
|
||||
nodes_with_gw = [n for n in health.nodes.values() if n.avg_gateways is not None]
|
||||
if nodes_with_gw:
|
||||
total_sources = len(self.data_store._sources)
|
||||
full = sum(1 for n in nodes_with_gw if n.avg_gateways >= total_sources)
|
||||
single = sum(1 for n in nodes_with_gw if n.avg_gateways <= 1.0)
|
||||
if single > 0:
|
||||
lines.append(f"📶 {full} nodes full coverage, {single} on thin ice with 1 gw")
|
||||
else:
|
||||
lines.append(f"📶 {full} nodes full coverage ✅")
|
||||
|
||||
high_util = [n for n in health.nodes.values()
|
||||
if n.channel_utilization is not None and n.channel_utilization > 15]
|
||||
if high_util:
|
||||
worst = max(high_util, key=lambda n: n.channel_utilization)
|
||||
lines.append(f"🔥 {worst.short_name} running hot at {worst.channel_utilization:.0f}% util")
|
||||
|
||||
infra_nodes = [n for n in health.nodes.values() if n.is_infrastructure]
|
||||
bat_low = [n for n in infra_nodes if n.battery_percent is not None and 0 < n.battery_percent < 20]
|
||||
if bat_low:
|
||||
low_names = ", ".join(n.short_name for n in bat_low)
|
||||
lines.append(f"🔋 ⚠️ {low_names} low battery")
|
||||
else:
|
||||
lines.append(f"🔋 All infra powered up ✅")
|
||||
|
||||
env_nodes = [n for n in health.nodes.values() if n.has_environment_sensor]
|
||||
if env_nodes:
|
||||
temps = [n.temperature for n in env_nodes if _is_valid_temperature(n.temperature)]
|
||||
if temps:
|
||||
lines.append(f"🌡️ {min(temps):.0f}–{max(temps):.0f}°C across {len(env_nodes)} sensors")
|
||||
|
||||
lines.append("")
|
||||
region_parts = []
|
||||
for region in health.regions:
|
||||
if not region.node_ids:
|
||||
continue
|
||||
rs = region.score
|
||||
context = self._region_context(region.name)
|
||||
name = context.split("(")[0].strip() if context else region.name
|
||||
return f"{name} {rs.composite:.0f}/100 | {rs.infra_online}/{rs.infra_total} infra | {rs.util_percent:.0f}% util"
|
||||
if scope == "node" and scope_value:
|
||||
return self.build_node_compact(scope_value)
|
||||
s = health.score
|
||||
lines = [f"Mesh {s.composite:.0f}/100 | {s.infra_online}/{s.infra_total} infra | {s.util_percent:.0f}% util"]
|
||||
for region in health.regions:
|
||||
if region.score.composite < 60:
|
||||
offline = region.score.infra_total - region.score.infra_online
|
||||
context = self._region_context(region.name)
|
||||
name = context.split("(")[0].strip() if context else region.name
|
||||
lines.append(f"! {name} {region.score.composite:.0f}/100 - {offline} infra offline")
|
||||
battery_warnings = self.health_engine.get_battery_warnings()
|
||||
for node in battery_warnings[:2]:
|
||||
if node.is_infrastructure:
|
||||
lines.append(f"! {node.short_name or str(node.node_num)} bat {node.battery_percent:.0f}%")
|
||||
return "\n".join(lines[:5])
|
||||
if rs.composite >= 90:
|
||||
region_parts.append(f"{name} ✅")
|
||||
elif rs.composite >= 70:
|
||||
region_parts.append(f"{name} ⚠️")
|
||||
else:
|
||||
region_parts.append(f"{name} ❌")
|
||||
if region_parts:
|
||||
lines.append(" | ".join(region_parts))
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _region_lora_compact(self, region) -> str:
|
||||
"""Compact region display for !region [name]."""
|
||||
rs = region.score
|
||||
context = self._region_context(region.name)
|
||||
name = context.split("(")[0].strip() if context else region.name
|
||||
|
||||
if rs.composite >= 90:
|
||||
header = f"📡 {name} looking good — {rs.composite:.0f}/100"
|
||||
elif rs.composite >= 70:
|
||||
header = f"📡 {name} needs attention — {rs.composite:.0f}/100"
|
||||
else:
|
||||
header = f"🚨 {name} in trouble — {rs.composite:.0f}/100"
|
||||
|
||||
lines = [header]
|
||||
lines.append(f"🏗️ {rs.infra_online}/{rs.infra_total} infra | 📡 {rs.util_percent:.0f}% util")
|
||||
|
||||
offline = []
|
||||
for nid_str in region.node_ids:
|
||||
try:
|
||||
nid = int(nid_str)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
node = self.health_engine.mesh_health.nodes.get(nid)
|
||||
if node and node.is_infrastructure and not node.is_online:
|
||||
offline.append(node.short_name or str(nid))
|
||||
if offline:
|
||||
lines.append(f" ❌ Down: {', '.join(offline)}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def list_regions_compact(self) -> str:
|
||||
"""List all regions with scores for !region command."""
|
||||
|
|
@ -1526,6 +1617,8 @@ class MeshReporter:
|
|||
return "No regions configured."
|
||||
lines = ["Regions:"]
|
||||
for region in health.regions:
|
||||
if not region.node_ids:
|
||||
continue
|
||||
s = region.score
|
||||
flag = _tier_flag(s.tier)
|
||||
context = self._region_context(region.name)
|
||||
|
|
|
|||
|
|
@ -488,11 +488,14 @@ class MessageRouter:
|
|||
bot_owner = self.config.bot.owner or "Unknown"
|
||||
|
||||
identity = (
|
||||
f"You are {bot_name}, an LLM-powered conversational assistant running on a "
|
||||
f"Meshtastic mesh network. Your managing operator is {bot_owner}. "
|
||||
f"You are {bot_name}, an LLM-powered assistant on the freq51 Meshtastic mesh network. "
|
||||
f"Your managing operator is {bot_owner}. "
|
||||
f"You are open source at github.com/zvx-echo6/meshai.\n\n"
|
||||
f"IDENTITY: Your name is {bot_name}. You respond to DMs only. You connect "
|
||||
f"to a Meshtastic node via TCP through meshtasticd.\n\n"
|
||||
f"IDENTITY: Your name is {bot_name}. You ARE a physical node on the mesh — "
|
||||
f"node !27780c47 (AIDA-N2). You have a real location, real GPS coordinates, "
|
||||
f"and real radio connections. When someone asks how far something is from you, "
|
||||
f"check the mesh data for your node's position and calculate. "
|
||||
f"You are NOT just software — you are a node that other nodes can see, hear, and route through.\n\n"
|
||||
)
|
||||
|
||||
# 2. Static system prompt from config
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue