feat(frontend): group command template slots into 4 logical fieldsets
Mirrors the notification-template page's group layout. Command slots now split by name prefix into Command Responses, Error Messages (rate_limited/no_results), Command Descriptions (desc_*), and Usage Examples (usage_*). Language picker, reset-all, and slot filter are hoisted above the groups so they apply across all fieldsets, and empty groups are hidden so providers without usage_* don't render empty headers. Drops the orphan cmdTemplateConfig.commandResponsesHint i18n key — hints.commandResponses replaces it.
This commit is contained in:
@@ -823,7 +823,11 @@
|
||||
"defaultCount": "How many results to return when the user doesn't specify a count (1-20).",
|
||||
"responseMode": "Media: send actual photos. Text: send filenames/links only. Media mode uses more bandwidth.",
|
||||
"botLocale": "Language for command descriptions in Telegram's menu and bot response messages.",
|
||||
"rateLimits": "Cooldown in seconds between uses of each command category per chat. 0 = no limit."
|
||||
"rateLimits": "Cooldown in seconds between uses of each command category per chat. 0 = no limit.",
|
||||
"commandResponses": "Reply templates for each /command. Use {variables} to inject dynamic data.",
|
||||
"commandErrors": "Fallback messages shown when a command can't run (rate-limited) or returns nothing.",
|
||||
"commandDescriptions": "Short menu blurbs Telegram shows next to each /command in the chat command picker.",
|
||||
"commandUsage": "Example invocations rendered inside /help to show users how to call each command."
|
||||
},
|
||||
"matrixBot": {
|
||||
"titleEmphasis": "matrix",
|
||||
@@ -876,7 +880,9 @@
|
||||
"noConfigs": "No command template configs yet.",
|
||||
"confirmDelete": "Delete this command template config?",
|
||||
"commandResponses": "Command Responses",
|
||||
"commandResponsesHint": "Leave a slot empty to use the default hardcoded response."
|
||||
"commandErrors": "Error Messages",
|
||||
"commandDescriptions": "Command Descriptions",
|
||||
"commandUsage": "Usage Examples"
|
||||
},
|
||||
"commandConfig": {
|
||||
"titleEmphasis": "configs",
|
||||
|
||||
@@ -823,7 +823,11 @@
|
||||
"defaultCount": "Сколько результатов возвращать, если пользователь не указал количество (1-20).",
|
||||
"responseMode": "Медиа: отправка фото. Текст: только имена файлов/ссылки. Медиа-режим использует больше трафика.",
|
||||
"botLocale": "Язык описаний команд в меню Telegram и ответов бота.",
|
||||
"rateLimits": "Кулдаун в секундах между использованиями команд в каждом чате. 0 = без ограничений."
|
||||
"rateLimits": "Кулдаун в секундах между использованиями команд в каждом чате. 0 = без ограничений.",
|
||||
"commandResponses": "Шаблоны ответов на каждую /команду. Используйте {переменные} для динамических данных.",
|
||||
"commandErrors": "Резервные сообщения, когда команда не может выполниться (превышен лимит) или ничего не возвращает.",
|
||||
"commandDescriptions": "Короткие подписи в меню команд Telegram, которые показываются рядом с каждой /командой.",
|
||||
"commandUsage": "Примеры вызовов, отображаемые в /help, чтобы показать пользователям как вызывать каждую команду."
|
||||
},
|
||||
"matrixBot": {
|
||||
"titleEmphasis": "matrix",
|
||||
@@ -876,7 +880,9 @@
|
||||
"noConfigs": "Шаблонов команд пока нет.",
|
||||
"confirmDelete": "Удалить этот шаблон команд?",
|
||||
"commandResponses": "Ответы команд",
|
||||
"commandResponsesHint": "Оставьте слот пустым, чтобы использовать ответ по умолчанию."
|
||||
"commandErrors": "Сообщения об ошибках",
|
||||
"commandDescriptions": "Описания команд",
|
||||
"commandUsage": "Примеры использования"
|
||||
},
|
||||
"commandConfig": {
|
||||
"titleEmphasis": "конфигурации",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
import JinjaEditor from '$lib/components/JinjaEditor.svelte';
|
||||
import CollapsibleSlot from '$lib/components/CollapsibleSlot.svelte';
|
||||
import Hint from '$lib/components/Hint.svelte';
|
||||
import EntitySelect, { type EntityItem } from '$lib/components/EntitySelect.svelte';
|
||||
import { getLocaleMeta } from '$lib/locales';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
@@ -126,11 +127,40 @@
|
||||
let commandSlots = $derived<SlotDef[]>(
|
||||
allCapabilities[form.provider_type]?.command_slots || []
|
||||
);
|
||||
let filteredCmdSlots = $derived(
|
||||
slotFilter
|
||||
? commandSlots.filter(s => s.name.toLowerCase().includes(slotFilter.toLowerCase()) || s.description.toLowerCase().includes(slotFilter.toLowerCase()))
|
||||
: commandSlots
|
||||
);
|
||||
|
||||
const ERROR_SLOTS = new Set(['rate_limited', 'no_results']);
|
||||
|
||||
/**
|
||||
* Group command slots by purpose so the form mirrors how notification
|
||||
* templates are split (event vs scheduled vs settings).
|
||||
*
|
||||
* commandResponses — primary reply templates (/start, /help, /status, data slots)
|
||||
* commandErrors — fallback messages (rate_limited, no_results)
|
||||
* commandDescriptions — desc_* slots: short menu blurbs in Telegram's command picker
|
||||
* commandUsage — usage_* slots: invocation examples shown by /help
|
||||
*/
|
||||
let commandSlotGroups = $derived([
|
||||
{
|
||||
group: 'commandResponses',
|
||||
slots: commandSlots.filter(s =>
|
||||
!s.name.startsWith('desc_') &&
|
||||
!s.name.startsWith('usage_') &&
|
||||
!ERROR_SLOTS.has(s.name)
|
||||
),
|
||||
},
|
||||
{
|
||||
group: 'commandErrors',
|
||||
slots: commandSlots.filter(s => ERROR_SLOTS.has(s.name)),
|
||||
},
|
||||
{
|
||||
group: 'commandDescriptions',
|
||||
slots: commandSlots.filter(s => s.name.startsWith('desc_')),
|
||||
},
|
||||
{
|
||||
group: 'commandUsage',
|
||||
slots: commandSlots.filter(s => s.name.startsWith('usage_')),
|
||||
},
|
||||
]);
|
||||
|
||||
/** Get slot template for current locale, with fallback. */
|
||||
function getSlotValue(slotName: string): string {
|
||||
@@ -424,10 +454,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<fieldset class="border border-[var(--color-border)] rounded-md p-3">
|
||||
<legend class="text-sm font-medium px-1">{t('cmdTemplateConfig.commandResponses')}</legend>
|
||||
<p class="text-xs text-[var(--color-muted-foreground)] mb-2">{t('cmdTemplateConfig.commandResponsesHint')}</p>
|
||||
|
||||
<!-- Language picker -->
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-xs font-medium text-[var(--color-muted-foreground)] shrink-0">
|
||||
@@ -453,14 +479,21 @@
|
||||
|
||||
<!-- Slot filter -->
|
||||
{#if commandSlots.length > 4}
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
<input type="text" bind:value={slotFilter} placeholder={t('templateConfig.filterSlots')}
|
||||
class="w-full px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-2">
|
||||
{#each filteredCmdSlots as slot}
|
||||
{#each commandSlotGroups.filter(g => g.slots.length > 0) as group}
|
||||
{@const filteredSlots = slotFilter ? group.slots.filter(s => s.name.toLowerCase().includes(slotFilter.toLowerCase()) || s.description.toLowerCase().includes(slotFilter.toLowerCase())) : group.slots}
|
||||
{#if filteredSlots.length > 0}
|
||||
<fieldset class="border border-[var(--color-border)] rounded-md p-3">
|
||||
<legend class="text-sm font-medium px-1">
|
||||
{t(`cmdTemplateConfig.${group.group}`)}<Hint text={t(`hints.${group.group}`)} />
|
||||
</legend>
|
||||
<div class="space-y-2 mt-2">
|
||||
{#each filteredSlots as slot}
|
||||
<CollapsibleSlot
|
||||
label={slot.name}
|
||||
description="/{slot.name} — {slot.description}"
|
||||
@@ -511,6 +544,8 @@
|
||||
{/each}
|
||||
</div>
|
||||
</fieldset>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<button type="submit" class="px-4 py-2 bg-[var(--color-primary)] text-[var(--color-primary-foreground)] rounded-md text-sm font-medium hover:opacity-90">
|
||||
{editing ? t('common.save') : t('common.create')}
|
||||
|
||||
Reference in New Issue
Block a user