feat: kanban status buttons, task back-nav, RSS UI, weather search, briefing fixes
Project view: - Add inline status advance buttons on kanban task cards (todo→in_progress, in_progress→done); buttons reveal on hover, stop link navigation Task viewer: - Back button navigates to task's project instead of /tasks when project_id set - Esc key navigates to project (or /tasks); blurs focused element first Quick capture: - Use user's configured model instead of hardcoded Config.OLLAMA_MODEL - Remove create_project from classifier prompt (tool not offered, caused task-shaped inputs to silently fall through to note fallback) Briefing scheduler: - Fix get_event_loop() → get_running_loop() so background thread uses the correct hypercorn event loop (jobs were scheduling but never executing) - Suppress bare greeting when both LLM synthesis lanes return empty RSS feed UI (SettingsView): - Show last-fetched age, category badge, and feed URL per row - Category input field when adding a feed - Refresh all button: fetches latest items, reloads list, toasts with count - Enter key submits add-feed form; better empty-state hint with example feeds Weather tool: - Accept any city/region name in addition to 'home'/'work'/'all' - Geocodes via Nominatim + fetches live from Open-Meteo for arbitrary queries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -246,7 +246,7 @@ def create_app() -> Quart:
|
||||
|
||||
# Start briefing scheduler
|
||||
from fabledassistant.services.briefing_scheduler import start_briefing_scheduler
|
||||
start_briefing_scheduler(asyncio.get_event_loop())
|
||||
start_briefing_scheduler(asyncio.get_running_loop())
|
||||
|
||||
@app.after_serving
|
||||
async def shutdown():
|
||||
|
||||
@@ -78,7 +78,8 @@ async def quick_capture_route():
|
||||
if not text:
|
||||
return jsonify({"error": "text is required"}), 400
|
||||
|
||||
model = Config.OLLAMA_MODEL
|
||||
from fabledassistant.services.settings import get_setting
|
||||
model = await get_setting(uid, "default_model", Config.OLLAMA_MODEL)
|
||||
|
||||
# Build tool list for this user, then restrict to capture-only operations.
|
||||
all_tools = await get_tools_for_user(uid)
|
||||
|
||||
@@ -274,6 +274,10 @@ async def run_compilation(user_id: int, slot: str, model: str | None = None) ->
|
||||
),
|
||||
)
|
||||
|
||||
if not internal_text and not external_text:
|
||||
logger.warning("Briefing compilation produced no content for user %d slot %s", user_id, slot)
|
||||
return ""
|
||||
|
||||
greeting = slot_greeting(slot)
|
||||
today = internal_data["date"]
|
||||
parts = [f"**{greeting} — {today}**", ""]
|
||||
|
||||
@@ -132,7 +132,6 @@ Rules:
|
||||
- create_event: appointments, meetings, scheduled occurrences with a date/time ("dentist Friday 2pm", "team meeting next Tuesday")
|
||||
- update_note: updating, editing, appending to an existing note or task ("add to my shopping list: eggs", "mark buy milk done", "append to my meeting notes", "update my project note")
|
||||
- research_topic: user wants a comprehensive research note from web sources ("research X", "look up X and make a note", "find everything about X", "compile a note on X")
|
||||
- create_project: user wants to create a project, initiative, or campaign ("new project X", "start a project called Y", "create a project for Z")
|
||||
- create_note: everything else — ideas, observations, links, excerpts, longer text
|
||||
- For create_task / create_event: extract a concise title; put any extra detail in "body"
|
||||
- For create_note: use a short descriptive title (≤60 chars); put the FULL original text as "body"
|
||||
|
||||
@@ -530,16 +530,19 @@ _CORE_TOOLS = [
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": (
|
||||
"Get the current 7-day weather forecast for the user's configured locations. "
|
||||
"Returns temperature, precipitation, and conditions for each day. "
|
||||
"Also reports any changes since the last forecast was fetched."
|
||||
"Get the 7-day weather forecast. Pass a city/region name to look up any location, "
|
||||
"or omit to get the user's configured home/work locations. "
|
||||
"Returns temperature, precipitation, and conditions for each day."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location_key": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "Which location to focus on: 'home', 'work', or 'all'. Defaults to 'all'.",
|
||||
"description": (
|
||||
"City, region, or country to look up — e.g. 'Portland, Oregon', 'Tokyo', 'home', 'work'. "
|
||||
"Use 'home' or 'work' to get the user's saved locations. Omit for all saved locations."
|
||||
),
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
@@ -1646,11 +1649,31 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict:
|
||||
return {"success": True, "log": log.to_dict(), "task": note.title}
|
||||
|
||||
elif tool_name == "get_weather":
|
||||
from fabledassistant.services.weather import get_cached_weather
|
||||
location_key = arguments.get("location_key", "all")
|
||||
from fabledassistant.services.weather import (
|
||||
get_cached_weather, geocode, _fetch_open_meteo, parse_forecast
|
||||
)
|
||||
from datetime import datetime, timezone as _tz
|
||||
location = (arguments.get("location") or "").strip()
|
||||
_known = {"home", "work", "all", ""}
|
||||
if location.lower() not in _known:
|
||||
# Live geocode + fetch for arbitrary city
|
||||
try:
|
||||
lat, lon, label = await geocode(location)
|
||||
raw = await _fetch_open_meteo(lat, lon)
|
||||
days = parse_forecast(raw)
|
||||
return {"data": {"locations": [{
|
||||
"location_key": "query",
|
||||
"location_label": label,
|
||||
"fetched_at": datetime.now(_tz.utc).isoformat(),
|
||||
"days": days,
|
||||
"changes_since_last_fetch": [],
|
||||
}]}}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"Could not get weather for '{location}': {e}"}
|
||||
# Fall back to cached configured locations
|
||||
locations = await get_cached_weather(user_id)
|
||||
if location_key != "all":
|
||||
locations = [loc for loc in locations if loc["location_key"] == location_key]
|
||||
if location.lower() not in ("all", ""):
|
||||
locations = [loc for loc in locations if loc["location_key"] == location.lower()]
|
||||
return {"data": {"locations": locations}}
|
||||
|
||||
elif tool_name == "get_rss_items":
|
||||
|
||||
Reference in New Issue
Block a user