Enhance tag management with cooldown feature and related updates

- Added a `cooldownSeconds` field to the `Tag` model, allowing for customizable cooldown periods between tag responses.
- Updated tag command handling to incorporate cooldown logic, preventing spam and improving user experience.
- Enhanced the WebUI to support cooldown configuration for tags, including input validation and localization for cooldown messages.
- Refactored tag-related service functions to manage cooldowns effectively, ensuring accurate tracking and enforcement.
- Improved localization for new cooldown-related messages in both English and German.
This commit is contained in:
TheOnlyMace
2026-07-25 16:50:46 +02:00
parent 5f932f5d63
commit bc4fae5415
21 changed files with 318 additions and 39 deletions

View File

@@ -42,6 +42,7 @@ const EMPTY_DRAFT = {
components: null as MessageComponentsV2 | null,
responseType: 'TEXT' as TagResponseType,
triggerWord: '',
cooldownSeconds: 15,
allowedRoleIds: [] as string[],
allowedChannelIds: [] as string[]
};
@@ -56,6 +57,7 @@ export function TagsManager({ guildId, initialTags }: TagsManagerProps) {
const [tags, setTags] = useState<LocalTag[]>(
initialTags.map((tag, index) => ({
...tag,
cooldownSeconds: tag.cooldownSeconds ?? 15,
embed: tag.embed ?? null,
components: tag.components ?? null,
clientKey: tag.id ?? `existing-${index}`
@@ -100,6 +102,7 @@ export function TagsManager({ guildId, initialTags }: TagsManagerProps) {
components: validated.components,
responseType: draft.responseType,
triggerWord: draft.triggerWord.trim() || null,
cooldownSeconds: draft.cooldownSeconds,
allowedRoleIds: draft.allowedRoleIds,
allowedChannelIds: draft.allowedChannelIds
})
@@ -142,6 +145,7 @@ export function TagsManager({ guildId, initialTags }: TagsManagerProps) {
components: validated.components,
responseType: tag.responseType,
triggerWord: tag.triggerWord,
cooldownSeconds: tag.cooldownSeconds,
allowedRoleIds: tag.allowedRoleIds,
allowedChannelIds: tag.allowedChannelIds
})
@@ -255,6 +259,22 @@ export function TagsManager({ guildId, initialTags }: TagsManagerProps) {
<Label>{t('modulePages.tags.triggerWord')}</Label>
<Input value={draft.triggerWord} onChange={(event) => setDraft((prev) => ({ ...prev, triggerWord: event.target.value }))} placeholder={t('common.optional')} />
</div>
<div className="space-y-2">
<Label>{t('modulePages.tags.cooldownSeconds')}</Label>
<Input
type="number"
min={0}
max={3600}
value={draft.cooldownSeconds}
onChange={(event) =>
setDraft((prev) => ({
...prev,
cooldownSeconds: Math.min(3600, Math.max(0, Number(event.target.value) || 0))
}))
}
/>
<p className="text-xs text-muted-foreground">{t('modulePages.tags.cooldownHint')}</p>
</div>
</div>
{renderResponseEditor(
draft.responseType,
@@ -325,6 +345,28 @@ export function TagsManager({ guildId, initialTags }: TagsManagerProps) {
<Label>{t('modulePages.tags.triggerWord')}</Label>
<Input value={tag.triggerWord ?? ''} onChange={(event) => setTags((prev) => prev.map((entry) => (entry.clientKey === tag.clientKey ? { ...entry, triggerWord: event.target.value || null } : entry)))} />
</div>
<div className="space-y-2">
<Label>{t('modulePages.tags.cooldownSeconds')}</Label>
<Input
type="number"
min={0}
max={3600}
value={tag.cooldownSeconds}
onChange={(event) =>
setTags((prev) =>
prev.map((entry) =>
entry.clientKey === tag.clientKey
? {
...entry,
cooldownSeconds: Math.min(3600, Math.max(0, Number(event.target.value) || 0))
}
: entry
)
)
}
/>
<p className="text-xs text-muted-foreground">{t('modulePages.tags.cooldownHint')}</p>
</div>
</div>
{renderResponseEditor(
tag.responseType,