mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
86 lines
3.5 KiB
Python
86 lines
3.5 KiB
Python
|
|
"""The library map: a compact, live overview of this WROLPi injected into every chat.
|
||
|
|
|
||
|
|
Small models answer from what is in context. Without this block the model must spend a tool
|
||
|
|
round-trip (minutes on slow hardware) just to learn what channels exist — and it still misread
|
||
|
|
spellings. With it, exact names, counts, and today's date are in front of the model before the
|
||
|
|
first token. Kept to a few hundred tokens: names are capped and the block is cached briefly."""
|
||
|
|
import time
|
||
|
|
from typing import List, Optional
|
||
|
|
|
||
|
|
from sqlalchemy import func
|
||
|
|
|
||
|
|
from wrolpi.common import logger
|
||
|
|
from wrolpi.dates import now
|
||
|
|
from wrolpi.db import get_db_session
|
||
|
|
|
||
|
|
logger = logger.getChild(__name__)
|
||
|
|
|
||
|
|
# At most this many names per category; a huge library lists the rest as a count.
|
||
|
|
MAX_NAMES = 30
|
||
|
|
CACHE_SECONDS = 60
|
||
|
|
|
||
|
|
_cache = dict(text=None, expires=0.0)
|
||
|
|
|
||
|
|
|
||
|
|
def _capped_names(names: List[str], label: str) -> str:
|
||
|
|
names = [i for i in names if i]
|
||
|
|
if not names:
|
||
|
|
return f'{label}: none'
|
||
|
|
listed = ', '.join(sorted(names)[:MAX_NAMES])
|
||
|
|
extra = f' (and {len(names) - MAX_NAMES} more)' if len(names) > MAX_NAMES else ''
|
||
|
|
return f'{label} ({len(names)}): {listed}{extra}'
|
||
|
|
|
||
|
|
|
||
|
|
def build_library_context() -> str:
|
||
|
|
"""Build the library overview. Every query here must stay cheap (counts and name lists)."""
|
||
|
|
from modules.archive.models import Archive
|
||
|
|
from modules.docs.models import Doc
|
||
|
|
from modules.videos.models import Video
|
||
|
|
from modules.zim.models import Zim
|
||
|
|
from wrolpi.collections.models import Collection
|
||
|
|
|
||
|
|
lines = [f'Today is {now().strftime("%A, %B %d, %Y")}.', "The user's library contains:"]
|
||
|
|
with get_db_session() as session:
|
||
|
|
counts = {model.__name__: session.query(func.count(model.id)).scalar() or 0
|
||
|
|
for model in (Video, Archive, Doc)}
|
||
|
|
lines.append(f'- {counts["Video"]} videos, {counts["Archive"]} archived web pages,'
|
||
|
|
f' {counts["Doc"]} documents/ebooks.')
|
||
|
|
|
||
|
|
collections = session.query(Collection.kind, Collection.name).all()
|
||
|
|
channels = [name for kind, name in collections if kind == 'channel']
|
||
|
|
domains = [name for kind, name in collections if kind == 'domain']
|
||
|
|
lines.append(f'- {_capped_names(channels, "Video channels")}')
|
||
|
|
lines.append(f'- {_capped_names(domains, "Archived websites")}')
|
||
|
|
|
||
|
|
zims = session.query(Zim).all()
|
||
|
|
zim_titles = []
|
||
|
|
for zim in zims:
|
||
|
|
try:
|
||
|
|
zim_titles.append(zim.zim_metadata.title)
|
||
|
|
except Exception:
|
||
|
|
zim_titles.append(zim.path.name if zim.path else None)
|
||
|
|
lines.append(f'- {_capped_names(zim_titles, "Zim encyclopedias")}')
|
||
|
|
|
||
|
|
try:
|
||
|
|
from modules.inventory.common import get_inventory_configs
|
||
|
|
inventories = [i.get('name') for i in get_inventory_configs().all_inventories()]
|
||
|
|
lines.append(f'- {_capped_names(inventories, "Inventories")}')
|
||
|
|
except Exception as e:
|
||
|
|
logger.debug('Library context could not list inventories', exc_info=e)
|
||
|
|
|
||
|
|
lines.append('Use the exact names above; user spellings may differ.')
|
||
|
|
return '\n'.join(lines)
|
||
|
|
|
||
|
|
|
||
|
|
def get_library_context() -> Optional[str]:
|
||
|
|
"""The cached library overview; None when it cannot be built (chat proceeds without it)."""
|
||
|
|
if _cache['text'] is not None and time.time() < _cache['expires']:
|
||
|
|
return _cache['text']
|
||
|
|
try:
|
||
|
|
text = build_library_context()
|
||
|
|
except Exception as e:
|
||
|
|
logger.warning('Could not build the library context', exc_info=e)
|
||
|
|
return _cache['text']
|
||
|
|
_cache.update(text=text, expires=time.time() + CACHE_SECONDS)
|
||
|
|
return text
|