"""The cards offered on the new-chat screen. A blank composer is the least helpful thing a chat client can show someone who has just installed one. These are three starting points an administrator can replace with their own. """ from __future__ import annotations import logging from sqlalchemy import func, select from sqlalchemy.orm import Session as DBSession from lembas.db.models import Suggestion from lembas.services import settings_store log = logging.getLogger(__name__) # A short list stays a short list. Past a dozen it is a menu, and a menu on the # empty screen is a worse blank page than a blank page. MAX_SUGGESTIONS = 12 MAX_SHOWN = 6 MAX_NAME = 120 MAX_DESCRIPTION = 300 MAX_PROMPT = 4000 # Each ends mid-sentence, so the caret lands exactly where the person has to # start typing. No Middle-earth flavour: this is functional UI. DEFAULTS: tuple[tuple[str, str, str], ...] = ( ( "Explain this", "Paste something confusing and get it back in plain language.", "Explain the following in plain language. Start with one sentence " "summarising it, then the details that actually matter, then anything I " "should watch out for. If I have not pasted anything yet, ask me for it " "rather than guessing.\n\n", ), ( "Draft a reply", "Turn a message you have received into an answer you can send.", "Help me reply to the message below. If the tone I want and the outcome " "I am after are not obvious from it, ask me before writing. Then give me " "a draft I could send as it stands.\n\n", ), ( "Find the flaw", "Have a plan argued with before you commit to it.", "I am going to describe a plan. Argue against it: what is most likely to " "go wrong, what am I assuming without evidence, and what would change " "your mind. Do not soften it, and do not agree just because I sound " "confident.\n\nMy plan: ", ), ) # Guards the seed. Not "is the table empty", because an administrator who # deletes all three would get them back on every restart. SEEDED_KEY = "suggestions_seeded" def visible(db: DBSession) -> list[Suggestion]: """What the new-chat screen shows, in order.""" return list( db.scalars( select(Suggestion) .where(Suggestion.enabled.is_(True)) .order_by(Suggestion.position, Suggestion.name) .limit(MAX_SHOWN) ) ) def all_of_them(db: DBSession) -> list[Suggestion]: """Every suggestion, enabled or not, for the admin page.""" return list( db.scalars(select(Suggestion).order_by(Suggestion.position, Suggestion.name)) ) def next_position(db: DBSession) -> int: """New rows land at the end rather than at 0, where they would shuffle. No `or -1` after the coalesce: position 0 is falsy, so that idiom sends the second row back to 0 on top of the first. """ highest = db.scalar(select(func.coalesce(func.max(Suggestion.position), -1))) return int(highest if highest is not None else -1) + 1 def create(db: DBSession, *, name: str, description: str = "", prompt: str = "") -> Suggestion: suggestion = Suggestion( name=name.strip()[:MAX_NAME], description=description.strip()[:MAX_DESCRIPTION], prompt=prompt[:MAX_PROMPT], position=next_position(db), ) db.add(suggestion) db.commit() return suggestion def seed_defaults(db: DBSession) -> int: """Write the built-in suggestions, once ever. Runs from the startup housekeeping block. The flag is what makes it once: checking whether the table is empty would restore all three every restart for anyone who decided they did not want them. """ if settings_store.get(db, SEEDED_KEY): return 0 for name, description, prompt in DEFAULTS: create(db, name=name, description=description, prompt=prompt) settings_store.update(db, {SEEDED_KEY: True}) log.info("seeded %d default suggestion(s)", len(DEFAULTS)) return len(DEFAULTS)