/** * 会话头部 QMT 连接快捷切换 chip + 健康状态控件(R-004 Q9/Q10;2026-09-01 重做) * * - 挂载 slot:conversation.session.header.actions(与 DSH 内置 PTC 模式标签并排) * - 形态: * - chip「QMT: <激活配置名> ▾」:点开下拉列出全部配置(激活项勾选),仅切换激活 * - chip 右侧独立「QMT 健康」控件:小圆点(绿=健康 / 红=异常),展示 QMT 连接状态 * - 数据:qmt-health 端点(服务端代理 GET 激活配置 /health) * * 2026-09-01:移除全盘订阅状态标记(暂缓,重开筛选做);QMT health 检查独立保留。 */ import { useState, useEffect, useRef, useCallback } from 'react'; import { useRpc } from './connection.jsx'; import { ToastProvider, useToast } from './Toast.jsx'; /** 下拉菜单项 */ function MenuItem({ item, selected, onSelect }) { return ( ); } /** * QMT 健康状态控件(2026-09-01) * - 位置:QMT 下拉框右侧 * - 显示:绿灯(health OK)/ 红灯(health 异常) * - 每 5s 轮询 qmt-health */ function QmtHealthIndicator() { const [health, setHealth] = useState(null); // { healthy, latencyMs, ... } | null(未获取) const [refreshing, setRefreshing] = useState(false); const call = useRpc(); // 读服务端缓存(默认,秒回) const load = useCallback(async () => { try { const res = await call('one-divine-lot/qmt-health', {}); if (res && res.ok) setHealth(res.value); // 失败保留上次状态 } catch (e) { /* 静默 */ } }, [call]); // 点击触发即时探测(refresh=true,刷新服务端缓存) const refresh = useCallback(async () => { if (refreshing) return; setRefreshing(true); try { const res = await call('one-divine-lot/qmt-health', { args: { refresh: true } }); if (res && res.ok) setHealth(res.value); } catch (e) { /* 静默 */ } finally { setRefreshing(false); } }, [call, refreshing]); useEffect(() => { load(); // 打开即读缓存 const timer = setInterval(load, 30000); // 30s 轮询缓存(服务端 5 分钟更新,前端低频同步) return () => clearInterval(timer); }, [load]); // 状态推导:null=灰(未知/加载中),healthy=true=绿,false=红 const color = health === null ? 'var(--dsw-alias-label-tertiary, #bbb)' : (health.healthy ? 'var(--dsw-alias-state-success-primary, #2e7d32)' : 'var(--dsw-alias-state-error-primary, #d32f2f)'); const title = health === null ? 'QMT 健康状态获取中…(点击立即检测)' : (health.healthy ? 'QMT 连接健康(' + (health.latencyMs ?? '-') + 'ms,检测于 ' + new Date(health.checkedAt ?? Date.now()).toLocaleTimeString() + ',点击重新检测)' : 'QMT 连接异常' + (health.healthError ? ':' + String(health.healthError).slice(0, 40) : '') + '(点击重新检测)'); return ( ); } function ChipInner() { const [state, setState] = useState(null); // { list, activeId, defaultId, active } const [open, setOpen] = useState(false); const [switching, setSwitching] = useState(false); const rootRef = useRef(null); const call = useRpc(); const toast = useToast(); const load = useCallback(async () => { try { const res = await call('one-divine-lot/qmt-connections', {}); if (res && res.ok) setState(res.value); } catch (e) { /* 头部控件静默失败,不打扰会话 */ } }, [call]); useEffect(() => { load(); }, [load]); // 外点关闭 useEffect(() => { if (!open) return; const onDocClick = (e) => { if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', onDocClick); return () => document.removeEventListener('mousedown', onDocClick); }, [open]); const activate = async (item) => { setOpen(false); if (!item || switching) return; if (state && item.id === state.activeId) return; setSwitching(true); try { const res = await call('one-divine-lot/qmt-connections/activate', { args: { id: item.id } }); if (res && res.ok) { setState(res.value); toast.success('QMT 连接已切换:' + item.name); } else { toast.error((res && res.error && res.error.message) || '切换失败'); } } catch (e) { toast.error(e.message); } finally { setSwitching(false); } }; if (!state) return null; // 服务端未就绪/加载失败时不显示 const active = state.active || state.list.find((c) => c.id === state.activeId) || null; const label = active ? active.name : (state.list.length === 0 ? '未配置' : '未激活'); return (
{/* QMT 健康状态控件(下拉框右侧,绿灯=健康/红灯=异常) */} {open && (
切换 QMT 连接
{state.list.length === 0 ? (
暂无连接配置,请进 设置 → 神之一手 → QMT 连接配置 添加
) : ( state.list.map((c) => ( )) )}
管理配置请进 设置 → 神之一手 → QMT 连接配置
)}
); } /** 导出(包 ToastProvider,与 SettingsSection 同模式) */ export function QmtConnectionChip(props) { return ( ); }