gpu_memory_percent is serialized as JSON null (not omitted) when no
GPU is present, but the template checked `!== undefined`, which is
true for null too. Calling .toFixed() on that null then threw during
render, blanking the whole widget instead of just hiding the GPU row.
Only showed up on GPU-less hosts like a Lightsail instance, not on
this dev machine which has a GPU.
Introduces websearch.py (Tavily) + knowledge.py to ground chat responses
in search results, tagging.py to tag user and assistant messages via LLM
and pick related topics for the Discord useless-tips job, and
word_memory.py to track recent conversation nouns (surfaced in the new
WordPool.vue sidebar) used to pick tip topics close to a related tag.