feat(hermes): support Discord plugin runtime config

This commit is contained in:
晴天
2026-05-23 06:41:49 +08:00
parent f188bb85f7
commit 6c947a1fec
6 changed files with 593 additions and 1 deletions

View File

@@ -26,6 +26,22 @@ const CHANNELS = [
secretFields: ['token'],
fields: [
{ key: 'token', labelKey: 'engine.hermesChannelBotToken', type: 'password', placeholder: 'MTA...' },
{ key: 'homeChannel', labelKey: 'engine.hermesChannelDiscordHomeChannel', type: 'text', placeholder: '123456789012345678' },
{ key: 'homeChannelName', labelKey: 'engine.hermesChannelDiscordHomeChannelName', type: 'text', placeholder: 'ops' },
],
advancedFields: [
{ key: 'freeResponseChannels', labelKey: 'engine.hermesChannelDiscordFreeResponseChannels', type: 'textarea', placeholderKey: 'engine.hermesChannelDiscordFreeResponseChannelsPh' },
{ key: 'allowedChannels', labelKey: 'engine.hermesChannelDiscordAllowedChannels', type: 'textarea', placeholderKey: 'engine.hermesChannelDiscordChannelListPh' },
{ key: 'ignoredChannels', labelKey: 'engine.hermesChannelDiscordIgnoredChannels', type: 'textarea', placeholderKey: 'engine.hermesChannelDiscordChannelListPh' },
{ key: 'noThreadChannels', labelKey: 'engine.hermesChannelDiscordNoThreadChannels', type: 'textarea', placeholderKey: 'engine.hermesChannelDiscordChannelListPh' },
{ key: 'historyBackfillLimit', labelKey: 'engine.hermesChannelDiscordHistoryBackfillLimit', type: 'text', placeholder: '12' },
{ key: 'replyToMode', labelKey: 'engine.hermesChannelDiscordReplyToMode', type: 'select', options: [['first', 'engine.hermesChannelDiscordReplyFirst'], ['all', 'engine.hermesChannelDiscordReplyAll'], ['off', 'engine.hermesChannelDiscordReplyOff']] },
],
advancedToggles: [
{ key: 'autoThread', labelKey: 'engine.hermesChannelDiscordAutoThread' },
{ key: 'reactions', labelKey: 'engine.hermesChannelDiscordReactions' },
{ key: 'threadRequireMention', labelKey: 'engine.hermesChannelDiscordThreadRequireMention' },
{ key: 'historyBackfill', labelKey: 'engine.hermesChannelDiscordHistoryBackfill' },
],
},
{
@@ -109,6 +125,13 @@ function defaultForm(platform) {
form.typingIndicator = true
form.resolveSenderNames = true
}
if (platform === 'discord') {
form.autoThread = true
form.reactions = true
form.threadRequireMention = false
form.historyBackfill = false
form.replyToMode = 'first'
}
if (platform === 'slack') form.webhookPath = '/slack/events'
return form
}
@@ -284,6 +307,25 @@ export function render() {
</div>
</div>
${(channel.advancedFields || []).length ? `
<div class="hm-channel-section">
<div class="hm-channel-section-title">${esc(t('engine.hermesChannelRuntimeBehavior'))}</div>
${(channel.advancedToggles || []).length ? `
<div class="hm-channel-toggle-grid">
${channel.advancedToggles.map(toggle => `
<label class="hm-channel-check">
<input class="hm-channel-input" data-key="${esc(toggle.key)}" type="checkbox" ${form[toggle.key] ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
<span>${esc(t(toggle.labelKey))}</span>
</label>
`).join('')}
</div>
` : ''}
<div class="hm-field-row">
${channel.advancedFields.map(field => renderField(field, form, disabled)).join('')}
</div>
</div>
` : ''}
<div class="hm-channel-footnote">
${icon('info', 14)}
<span>${esc(t('engine.hermesChannelRestartHint'))}</span>

View File

@@ -937,7 +937,25 @@ export default {
hermesChannelSaveFailed: _('保存渠道配置失败', 'Failed to save channel configuration', '儲存頻道設定失敗'),
hermesChannelCredentials: _('凭证', 'Credentials', '憑證'),
hermesChannelAccessPolicy: _('访问策略', 'Access Policy', '存取策略'),
hermesChannelRuntimeBehavior: _('运行行为', 'Runtime Behavior', '執行行為'),
hermesChannelBotToken: _('Bot Token', 'Bot Token', 'Bot Token'),
hermesChannelDiscordHomeChannel: _('默认频道 ID', 'Home Channel ID', '預設頻道 ID'),
hermesChannelDiscordHomeChannelName: _('默认频道名称', 'Home Channel Name', '預設頻道名稱'),
hermesChannelDiscordFreeResponseChannels: _('免 @ 响应频道', 'Free Response Channels', '免 @ 回應頻道'),
hermesChannelDiscordAllowedChannels: _('允许频道', 'Allowed Channels', '允許頻道'),
hermesChannelDiscordIgnoredChannels: _('忽略频道', 'Ignored Channels', '忽略頻道'),
hermesChannelDiscordNoThreadChannels: _('不自动建线程频道', 'No-thread Channels', '不自動建立討論串頻道'),
hermesChannelDiscordChannelListPh: _('每行或逗号分隔一个 Discord 频道 ID。', 'One Discord channel ID per line or comma-separated.', '每行或逗號分隔一個 Discord 頻道 ID。'),
hermesChannelDiscordFreeResponseChannelsPh: _('这些频道内无需 @Bot 即可响应;支持频道或父频道 ID。', 'Messages in these channels do not require @mention; channel or parent channel IDs are supported.', '這些頻道內無需 @Bot 即可回應;支援頻道或父頻道 ID。'),
hermesChannelDiscordHistoryBackfillLimit: _('上下文回填条数', 'History Backfill Limit', '上下文回填筆數'),
hermesChannelDiscordReplyToMode: _('回复模式', 'Reply Mode', '回覆模式'),
hermesChannelDiscordReplyFirst: _('仅首条回复引用', 'Reply to first message', '僅首則回覆引用'),
hermesChannelDiscordReplyAll: _('每条回复都引用', 'Reply to every message', '每則回覆都引用'),
hermesChannelDiscordReplyOff: _('不引用回复', 'No reply reference', '不引用回覆'),
hermesChannelDiscordAutoThread: _('自动创建线程', 'Auto-create threads', '自動建立討論串'),
hermesChannelDiscordReactions: _('启用表情反馈', 'Enable reactions', '啟用表情回饋'),
hermesChannelDiscordThreadRequireMention: _('线程内也要求 @Bot', 'Require @mention in threads', '討論串內也要求 @Bot'),
hermesChannelDiscordHistoryBackfill: _('触发时回填频道上下文', 'Backfill channel context on trigger', '觸發時回填頻道上下文'),
hermesChannelSlackBotToken: _('Bot Token (xoxb)', 'Bot Token (xoxb)', 'Bot Token (xoxb)'),
hermesChannelSlackAppToken: _('App Token (xapp)', 'App Token (xapp)', 'App Token (xapp)'),
hermesChannelSigningSecret: _('Signing Secret', 'Signing Secret', 'Signing Secret'),

View File

@@ -146,6 +146,19 @@ function bindClick(page) {
if (option) chooseWithAnimation(page, panel, option, engine)
})
// CTA 按钮本身位于内容层,不一定会命中背景三角形面板;
// 单独绑定可以保证移动端和键盘操作都能真正完成引擎选择。
page.querySelectorAll('[data-engine-cta]').forEach(btn => {
btn.addEventListener('click', (event) => {
event.stopPropagation()
if (_busy) return
const engine = btn.dataset.engineCta
const option = PRIMARY_OPTIONS.find(o => o.id === engine)
const panel = page.querySelector(`.es-panel[data-engine="${engine}"]`)
if (option && panel) chooseWithAnimation(page, panel, option, engine)
})
})
// 次级链接:两个都要 / 稍后再说(无对角线动画,直接走选择)
page.querySelectorAll('[data-secondary]').forEach(btn => {
btn.addEventListener('click', async (event) => {