feat(channels): add messaging channels and built-in qq bot

This commit is contained in:
晴天
2026-03-10 03:33:21 +08:00
parent 9ed58af426
commit a2f8ed9d54
19 changed files with 1248 additions and 21 deletions

View File

@@ -137,6 +137,50 @@ export function showModal({ title, fields, onConfirm }) {
if (firstInput) firstInput.focus()
}
/**
* 通用内容弹窗 — 支持自定义 HTML 和按钮
* @param {{ title, content, buttons, width }} opts
* buttons: [{ label, className, id }]
* @returns {HTMLElement} overlay 元素(带 .close() 方法)
*/
export function showContentModal({ title, content, buttons = [], width = 480 }) {
const overlay = document.createElement('div')
overlay.className = 'modal-overlay'
const btnsHtml = buttons.map(b =>
`<button class="${b.className || 'btn btn-primary btn-sm'}" id="${b.id || ''}">${b.label}</button>`
).join('')
overlay.innerHTML = `
<div class="modal" style="max-width:${width}px">
<div class="modal-title">${title}</div>
<div class="modal-content-body">${content}</div>
<div class="modal-actions">
<button class="btn btn-secondary btn-sm" data-action="cancel">取消</button>
${btnsHtml}
</div>
</div>
`
document.body.appendChild(overlay)
overlay.close = () => overlay.remove()
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.remove()
})
overlay.querySelector('[data-action="cancel"]').onclick = () => overlay.remove()
overlay.addEventListener('keydown', (e) => {
if (e.key === 'Escape') overlay.remove()
})
// 自动聚焦第一个输入框或按钮
const firstInput = overlay.querySelector('input, textarea, select')
if (firstInput) firstInput.focus()
return overlay
}
/**
* 升级进度弹窗 — 带进度条和实时日志
* @returns {{ appendLog, setProgress, setDone, setError, destroy }}

View File

@@ -25,6 +25,7 @@ const NAV_ITEMS_FULL = [
{ route: '/models', label: '模型配置', icon: 'models' },
{ route: '/agents', label: 'Agent 管理', icon: 'agents' },
{ route: '/gateway', label: 'Gateway', icon: 'gateway' },
{ route: '/channels', label: '消息渠道', icon: 'channels' },
{ route: '/security', label: '安全设置', icon: 'security' },
]
},
@@ -88,6 +89,7 @@ const ICONS = {
security: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>',
skills: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg>',
docker: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="11" width="4" height="3" rx=".5"/><rect x="6" y="11" width="4" height="3" rx=".5"/><rect x="11" y="11" width="4" height="3" rx=".5"/><rect x="6" y="7" width="4" height="3" rx=".5"/><rect x="11" y="7" width="4" height="3" rx=".5"/><rect x="16" y="11" width="4" height="3" rx=".5"/><rect x="11" y="3" width="4" height="3" rx=".5"/><path d="M2 17c1 3 4 5 10 5s9-2 10-5"/></svg>',
channels: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>',
debug: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/><circle cx="12" cy="12" r="3"/></svg>',
}