fix(bridge): add complete OBv11 fields matching v1 ethend

- OBv11: mode (client/server), remote_url, access_token, http_url,
  http_token, bot_port, send_interval_ms, admin_uids (8 fields)
- Telegram: bot_token, webhook_url, admin_uids
- Discord/Webhook: proper fields with labels
- Mode dropdown selector for client/server
- Field labels and placeholders matching v1
This commit is contained in:
Aska committed 2026-08-12 18:12:39 +08:00
1 parent 9eaed2883f
commit ba42ea2b45
1 file changed
+54 -24
+54 -24
View File
@@ -1,17 +1,34 @@
import { useEffect, useState } from 'react'
import { getToken } from '../../api/client'
const platformTypes = [
{ value: 'qq-obv11', label: 'QQ (OBv11)', fields: ['ws_url', 'access_token', 'bot_qq'] },
{ value: 'telegram', label: 'Telegram', fields: ['token'] },
{ value: 'discord', label: 'Discord', fields: ['token'] },
{ value: 'webhook', label: 'Webhook', fields: ['listen_addr'] },
]
interface FieldDef { key: string; label: string; placeholder: string; type?: string }
interface PlatformDef { value: string; label: string; fields: FieldDef[] }
const fieldLabels: Record<string, string> = {
ws_url: 'WebSocket 地址', access_token: 'Access Token', bot_qq: 'Bot QQ 号',
token: 'Bot Token', listen_addr: '监听地址',
}
const platformTypes: PlatformDef[] = [
{ value: 'qq-obv11', label: 'QQ (OBv11)', fields: [
{ key: 'mode', label: '连接模式', placeholder: '', type: 'select' },
{ key: 'remote_url', label: 'NapCat OneBot WS 地址', placeholder: 'ws://127.0.0.1:10311' },
{ key: 'access_token', label: 'Access Token (可选)', placeholder: '留空则不验证' },
{ key: 'http_url', label: 'NapCat HTTP API 地址 (可选)', placeholder: 'http://127.0.0.1:6099' },
{ key: 'http_token', label: 'HTTP API Token (可选)', placeholder: '留空则使用 Access Token' },
{ key: 'bot_port', label: '本地监听端口', placeholder: '8096' },
{ key: 'send_interval_ms', label: '消息发送间隔 (ms, 防封控)', placeholder: '2000' },
{ key: 'admin_uids', label: '管理员 QQ 号 (逗号分隔)', placeholder: '123456789,987654321' },
]},
{ value: 'telegram', label: 'Telegram', fields: [
{ key: 'bot_token', label: 'Bot Token', placeholder: '123456:ABC-DEF...' },
{ key: 'webhook_url', label: 'Webhook URL', placeholder: 'https://your-domain.com' },
{ key: 'admin_uids', label: '管理员账号ID (逗号分隔)', placeholder: '123456789,987654321' },
]},
{ value: 'discord', label: 'Discord', fields: [
{ key: 'bot_token', label: 'Bot Token', placeholder: '...' },
{ key: 'webhook_url', label: 'Webhook URL (可选)', placeholder: 'https://...' },
]},
{ value: 'webhook', label: 'Webhook', fields: [
{ key: 'webhook_url', label: 'Webhook URL', placeholder: 'https://hook.example.com/chat' },
{ key: 'secret', label: 'Secret Token (可选)', placeholder: '' },
]},
]
interface Adapter { name:string; type:string; enabled:boolean; settings:Record<string,string>; created_at:string }
@@ -58,7 +75,7 @@ export default function BridgePage() {
const startEdit = (a?: Adapter) => {
if (a) { setName(a.name); setAdType(a.type); setSettings(a.settings || {}) }
else { setName(''); setSettings({}) }
else { setName(''); setAdType('qq-obv11'); setSettings({}) }
setEditing(true)
}
@@ -91,22 +108,23 @@ export default function BridgePage() {
style={{ background: 'var(--mx-surface)', borderColor: 'var(--mx-outline)' }}
>
<div className="flex items-center gap-3">
<div className={`w-2.5 h-2.5 rounded-full ${a.enabled ? '' : ''}`}
<div className="w-2.5 h-2.5 rounded-full"
style={{ background: a.enabled ? 'var(--mx-tertiary-text)' : 'var(--mx-outline)' }} />
<div>
<p className="text-sm" style={{ color: 'var(--mx-text)' }}>{a.name}</p>
<p className="text-xs" style={{ color: 'var(--mx-text-dim)' }}>
{platformTypes.find(p=>p.value===a.type)?.label || a.type}
{a.settings?.mode && ` · ${a.settings.mode === 'server' ? '服务端模式' : '客户端模式'}`}
{a.created_at && ` · ${a.created_at.slice(0,10)}`}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<button onClick={() => toggle(a)}
className={`w-11 h-6 rounded-full relative transition-colors`}
className="w-11 h-6 rounded-full relative transition-colors"
style={{ background: a.enabled ? 'var(--mx-primary)' : 'var(--mx-outline)' }}
>
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-transform ${a.enabled ? 'translate-x-5.5' : 'translate-x-0.5'}`} />
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white shadow transition-transform ${a.enabled ? 'translate-x-5.5' : 'translate-x-0.5'}`} />
</button>
<button onClick={() => startEdit(a)} className="text-xs px-2 py-1 rounded-full" style={{ color: 'var(--mx-text-dim)' }}>编辑</button>
<button onClick={() => remove(a.name)} className="text-xs px-2 py-1 rounded-full" style={{ color: 'var(--mx-error)' }}>删除</button>
@@ -132,28 +150,40 @@ export default function BridgePage() {
</div>
{fields.map(f => (
<input key={f} value={settings[f] || ''} onChange={e => setSettings({...settings, [f]: e.target.value})}
placeholder={fieldLabels[f] || f}
className="w-full rounded-[14px] border px-3 py-2 text-sm outline-none"
style={{ background: 'var(--mx-surface)', borderColor: 'var(--mx-outline)', color: 'var(--mx-text)' }} />
<div key={f.key} className="space-y-1">
<label className="text-xs" style={{ color: 'var(--mx-text-dim)' }}>{f.label}</label>
{f.type === 'select' ? (
<select value={settings[f.key] || 'client'} onChange={e => setSettings({...settings, [f.key]: e.target.value})}
className="w-full rounded-[14px] border px-3 py-2 text-sm outline-none"
style={{ background: 'var(--mx-surface)', borderColor: 'var(--mx-outline)', color: 'var(--mx-text)' }}>
<option value="client">客户端模式 (主动连接 NapCat)</option>
<option value="server">服务端模式 (等待 NapCat 连接)</option>
</select>
) : (
<input value={settings[f.key] || ''} onChange={e => setSettings({...settings, [f.key]: e.target.value})}
placeholder={f.placeholder}
className="w-full rounded-[14px] border px-3 py-2 text-sm outline-none"
style={{ background: 'var(--mx-surface)', borderColor: 'var(--mx-outline)', color: 'var(--mx-text)' }} />
)}
</div>
))}
<div className="flex gap-2">
<div className="flex gap-2 pt-2">
<button onClick={save} className="px-4 py-2 rounded-full text-sm font-medium text-white" style={{ background: 'var(--mx-primary)' }}>保存</button>
<button onClick={() => setEditing(false)} className="px-4 py-2 rounded-full text-sm" style={{ border: '1px solid var(--mx-outline)', color: 'var(--mx-text-dim)' }}>取消</button>
</div>
</div>
)}
{/* Group chat rules */}
<div className="rounded-[18px] border p-4 mt-4" style={{ background: 'var(--mx-surface)', borderColor: 'var(--mx-outline)' }}>
{/* Rules */}
<div className="rounded-[18px] border p-4" style={{ background: 'var(--mx-surface)', borderColor: 'var(--mx-outline)' }}>
<h3 className="text-sm font-medium mb-2" style={{ color: 'var(--mx-text)' }}>群聊行为规则</h3>
<ul className="text-xs space-y-1 list-disc list-inside" style={{ color: 'var(--mx-text-dim)' }}>
<li>消息最大 500 字,超出截断</li>
<li>消息最大 500 字,超出自动截断</li>
<li>最短发送间隔 3 秒</li>
<li>每轮最多 2 条消息</li>
<li>非 @ 消息走审查模式</li>
<li>闭嘴关键词:闭嘴 / 别说话 / 安静</li>
<li>非 @ 消息走审查模式(LLM 自主判断是否回复)</li>
<li>闭嘴关键词:闭嘴 / 别说话 / 安静 / shut up</li>
</ul>
</div>
</div>