Add Discord-native hybrid front-end for Jarvis (bot + bridge)
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
Transform isair/jarvis into a Discord-controlled voice assistant running on the Ubuntu VNC desktop, keeping the mature ~39k-line Python brain intact. - bot/ (Node + bun, discord.js): /자비스 slash commands (ephemeral), voice channel join + voice receive/playback, pluggable VNC screen broadcast (selfbot live / noVNC / screenshot) - bridge/ (Python, Flask): wraps jarvis STT + run_reply_engine + Piper TTS behind a thin localhost HTTP API - .env.example, scripts/ (start_bridge/start_bot/dev), README rewrite, docs/language-comparison.md and docs/vnc-xfce-setup.md Language decision: hybrid (Python brain + Node/bun Discord layer) because Discord blocks bot video; native screen broadcast only works via a Node selfbot library.
This commit is contained in:
111
src/jarvis/tools/builtin/nutrition/fetch_meals.py
Normal file
111
src/jarvis/tools/builtin/nutrition/fetch_meals.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""Fetch meals tool for nutrition tracking."""
|
||||
|
||||
from typing import Dict, Any, Optional, List, Callable
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
from ....debug import debug_log
|
||||
from ...base import Tool, ToolContext
|
||||
from ...types import ToolExecutionResult
|
||||
|
||||
|
||||
def _normalize_time_range(args: Optional[Dict[str, Any]]) -> tuple[str, str]:
|
||||
"""Normalize time range for meal fetching."""
|
||||
now = datetime.now(timezone.utc)
|
||||
since: Optional[str] = None
|
||||
until: Optional[str] = None
|
||||
if args and isinstance(args, dict):
|
||||
try:
|
||||
since_val = args.get("since_utc")
|
||||
since = str(since_val) if since_val else None
|
||||
except Exception:
|
||||
since = None
|
||||
try:
|
||||
until_val = args.get("until_utc")
|
||||
until = str(until_val) if until_val else None
|
||||
except Exception:
|
||||
until = None
|
||||
if since is None and until is None:
|
||||
# Default last 24h
|
||||
return (now - timedelta(days=1)).isoformat(), now.isoformat()
|
||||
if since is None and until is not None:
|
||||
# backfill 24h prior to until
|
||||
try:
|
||||
until_dt = datetime.fromisoformat(until.replace("Z", "+00:00"))
|
||||
except Exception:
|
||||
until_dt = now
|
||||
return (until_dt - timedelta(days=1)).isoformat(), until_dt.isoformat()
|
||||
if since is not None and until is None:
|
||||
return since, now.isoformat()
|
||||
return since or (now - timedelta(days=1)).isoformat(), until or now.isoformat()
|
||||
|
||||
|
||||
def summarize_meals(meals: List[Any]) -> str:
|
||||
"""Summarize a list of meals with totals."""
|
||||
lines: List[str] = []
|
||||
total_kcal = 0.0
|
||||
total_protein = 0.0
|
||||
total_carbs = 0.0
|
||||
total_fat = 0.0
|
||||
for m in meals:
|
||||
try:
|
||||
desc = m["description"] if isinstance(m, dict) else m["description"]
|
||||
except Exception:
|
||||
desc = "meal"
|
||||
try:
|
||||
kcal = float(m["calories_kcal"]) if m["calories_kcal"] is not None else 0.0
|
||||
except Exception:
|
||||
kcal = 0.0
|
||||
try:
|
||||
prot = float(m["protein_g"]) if m["protein_g"] is not None else 0.0
|
||||
except Exception:
|
||||
prot = 0.0
|
||||
try:
|
||||
carbs = float(m["carbs_g"]) if m["carbs_g"] is not None else 0.0
|
||||
except Exception:
|
||||
carbs = 0.0
|
||||
try:
|
||||
fat = float(m["fat_g"]) if m["fat_g"] is not None else 0.0
|
||||
except Exception:
|
||||
fat = 0.0
|
||||
total_kcal += kcal
|
||||
total_protein += prot
|
||||
total_carbs += carbs
|
||||
total_fat += fat
|
||||
lines.append(f"- {desc} (~{int(round(kcal))} kcal, {int(round(prot))}g P, {int(round(carbs))}g C, {int(round(fat))}g F)")
|
||||
header = f"Meals: {len(meals)} | Total ~{int(round(total_kcal))} kcal, {int(round(total_protein))}g P, {int(round(total_carbs))}g C, {int(round(total_fat))}g F"
|
||||
return header + ("\n" + "\n".join(lines) if lines else "")
|
||||
|
||||
|
||||
class FetchMealsTool(Tool):
|
||||
"""Tool for fetching meals from the nutrition database."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "fetchMeals"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Retrieve meals from the database for a given time range with nutritional summary."
|
||||
|
||||
@property
|
||||
def inputSchema(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"since_utc": {"type": "string", "description": "Start time in ISO format (UTC)"},
|
||||
"until_utc": {"type": "string", "description": "End time in ISO format (UTC)"}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
|
||||
def run(self, args: Optional[Dict[str, Any]], context: ToolContext) -> ToolExecutionResult:
|
||||
"""Execute the fetch meals tool."""
|
||||
context.user_print("📖 Retrieving your meals…")
|
||||
since, until = _normalize_time_range(args if isinstance(args, dict) else None)
|
||||
debug_log(f"fetchMeals: range since={since} until={until}", "nutrition")
|
||||
meals = context.db.get_meals_between(since, until)
|
||||
debug_log(f"fetchMeals: count={len(meals)}", "nutrition")
|
||||
summary = summarize_meals([dict(r) for r in meals])
|
||||
# Return raw meal summary for profile processing
|
||||
context.user_print("✅ Meals retrieved.")
|
||||
return ToolExecutionResult(success=True, reply_text=summary)
|
||||
Reference in New Issue
Block a user