Prompt suggestions on the new-chat screen

A blank composer is the least helpful thing a chat client can show someone
who has just installed one. Three cards now sit under the empty state, and
an administrator manages them at /admin/suggestions.

Clicking a card fills the composer and stops there. It deliberately does not
send: every default ends mid-sentence, because a card is a starting point
rather than a question somebody already asked, and the caret lands where the
person has to start typing.

Seeding is guarded by a settings flag, not by "is the table empty" --
otherwise an administrator who decided against them would get all three back
on every restart. Capped at twelve, six shown: past a dozen this is a menu,
and a menu on the empty screen is a worse blank page than a blank page.

The cards are gated on there being no chat at all, not on the thread being
empty. An empty chat someone opened on purpose already has a model and a
prompt chosen.

Also fixes a pre-existing bug the position test caught. Both this and
_refresh_models wrote `coalesce(max(position), -1) or -1`, and position 0 is
falsy -- so the second row landed back on 0 on top of the first. The
coalesce was already doing that job; the `or` was undoing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-01 00:49:12 +02:00
parent 248dec2961
commit 09cfde4de8
13 changed files with 650 additions and 1 deletions
+2
View File
@@ -41,6 +41,7 @@ from lembas.db.models.library import (
chat_knowledge_bases,
)
from lembas.db.models.setting import Setting
from lembas.db.models.suggestion import Suggestion
from lembas.db.models.user import (
ROLE_ADMIN,
ROLE_PENDING,
@@ -85,6 +86,7 @@ __all__ = [
"Share",
"Skill",
"SkillRevision",
"Suggestion",
"User",
"chat_knowledge_bases",
"model_groups",
+32
View File
@@ -0,0 +1,32 @@
"""Starting points offered on the new-chat screen."""
from __future__ import annotations
from sqlalchemy import Boolean, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from lembas.db.base import Base, Timestamps, UUIDPrimaryKey
class Suggestion(UUIDPrimaryKey, Timestamps, Base):
"""One card on the empty chat screen.
Instance-wide rather than per-user: these are what an administrator wants
people to start with, the same way the instance system prompt is. There is
no owner_id and therefore nothing for `sharing` to decide.
"""
__tablename__ = "suggestions"
name: Mapped[str] = mapped_column(String(120), nullable=False)
description: Mapped[str] = mapped_column(String(300), default="")
# What lands in the composer. Deliberately not sent on its own: it usually
# ends mid-sentence, because a card is a starting point rather than a
# question somebody already asked.
prompt: Mapped[str] = mapped_column(Text, default="")
enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
position: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
def __repr__(self) -> str:
return f"<Suggestion {self.name}>"