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:
@@ -47,6 +47,11 @@
|
||||
{{ icon("sparkle", "icon--sm") }}
|
||||
<span class="nav-item__label">Prompts</span>
|
||||
</a>
|
||||
<a class="nav-item {{ 'is-active' if section == 'suggestions' }}"
|
||||
href="/admin/suggestions">
|
||||
{{ icon("star", "icon--sm") }}
|
||||
<span class="nav-item__label">Suggestions</span>
|
||||
</a>
|
||||
<a class="nav-item {{ 'is-active' if section == 'users' }}" href="/admin/users">
|
||||
{{ icon("user", "icon--sm") }}
|
||||
<span class="nav-item__label">Users</span>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
{% extends "admin/_layout.html" %}
|
||||
{% from "_macros.html" import icon %}
|
||||
{% set section = "suggestions" %}
|
||||
|
||||
{% block title %}Suggestions - LLeMbas{% endblock %}
|
||||
{% block heading %}Suggestions{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
<p class="admin-lede">
|
||||
Cards on the new-chat screen. Clicking one puts its prompt in the composer
|
||||
without sending it — the built-in ones deliberately end mid-sentence, so the
|
||||
caret lands where the person has to start typing. The first
|
||||
{{ max_shown }} enabled ones are shown, in this order.
|
||||
</p>
|
||||
|
||||
{% if saved %}
|
||||
<div class="alert alert--success">{{ icon("check", "icon--sm") }} <span>{{ saved }}</span></div>
|
||||
{% endif %}
|
||||
|
||||
{% for suggestion in suggestions %}
|
||||
<form class="card" method="post" action="/admin/suggestions/{{ suggestion.id }}">
|
||||
<div class="card__header">
|
||||
<h2 class="card__title">
|
||||
{{ suggestion.name }}
|
||||
{% if not suggestion.enabled %}<span class="badge">hidden</span>{% endif %}
|
||||
{% if loop.index > max_shown and suggestion.enabled %}
|
||||
<span class="badge badge--warning" title="Only the first {{ max_shown }} are shown">
|
||||
below the cut
|
||||
</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<button class="btn btn--sm btn--danger" type="submit"
|
||||
formaction="/admin/suggestions/{{ suggestion.id }}/delete"
|
||||
data-confirm-button="Delete the suggestion “{{ suggestion.name }}”?"
|
||||
data-confirm-title="Delete suggestion">
|
||||
{{ icon("trash", "icon--sm") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid--2">
|
||||
<div class="field">
|
||||
<label class="field__label" for="name-{{ suggestion.id }}">Name</label>
|
||||
<input class="input" id="name-{{ suggestion.id }}" name="name"
|
||||
value="{{ suggestion.name }}" maxlength="120">
|
||||
<p class="field__hint">The heading on the card.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="position-{{ suggestion.id }}">Position</label>
|
||||
<input class="input" id="position-{{ suggestion.id }}" name="position" type="number"
|
||||
min="1" value="{{ suggestion.position + 1 }}">
|
||||
<p class="field__hint">Order on the screen, lowest first.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="description-{{ suggestion.id }}">Description</label>
|
||||
<input class="input" id="description-{{ suggestion.id }}" name="description"
|
||||
value="{{ suggestion.description }}" maxlength="300">
|
||||
<p class="field__hint">One line under the name, saying what it is for.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="prompt-{{ suggestion.id }}">Prompt</label>
|
||||
<textarea class="textarea" id="prompt-{{ suggestion.id }}" name="prompt"
|
||||
rows="4">{{ suggestion.prompt }}</textarea>
|
||||
<p class="field__hint">
|
||||
Put in the composer, not sent. Ending it mid-sentence is usually right:
|
||||
the person still has to say what they are asking about.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="enabled" value="true" {{ 'checked' if suggestion.enabled }}>
|
||||
<span>Show this one</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="card__footer">
|
||||
<button class="btn btn--primary btn--sm" type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="empty">
|
||||
{{ icon("sparkle", "empty__mark") }}
|
||||
<h2 class="empty__title">No suggestions</h2>
|
||||
<p class="empty__text">The new-chat screen shows its empty state instead.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if not at_limit %}
|
||||
<form class="card" method="post" action="/admin/suggestions">
|
||||
<h2 class="card__title">Add a suggestion</h2>
|
||||
<div class="field">
|
||||
<label class="field__label" for="new-name">Name</label>
|
||||
<input class="input" id="new-name" name="name" maxlength="120"
|
||||
placeholder="Summarise a document" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="new-description">Description</label>
|
||||
<input class="input" id="new-description" name="description" maxlength="300"
|
||||
placeholder="What it is for, in one line.">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="new-prompt">Prompt</label>
|
||||
<textarea class="textarea" id="new-prompt" name="prompt" rows="4"
|
||||
placeholder="What lands in the composer when the card is clicked."></textarea>
|
||||
</div>
|
||||
<div class="card__footer">
|
||||
<button class="btn btn--primary" type="submit">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="admin-lede">
|
||||
{{ max_suggestions }} is the limit. Delete one to add another — past a dozen
|
||||
this is a menu, and a menu on the empty screen is a worse blank page than a
|
||||
blank page.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user